false, minVersion: '9' ); $compat = new Compatibility([$profile]); self::assertSame(ReleaseState::Released, $compat->matrix($profile)->state()); self::assertNull($compat->lockOf(self::CAPS)); self::assertFalse($compat->active($profile)); self::assertSame(['Fake: lit_mcp_missing_function()'], $compat->problems($profile)); } public function testLockOnFailureLocksOnlyWhatAHardProblemConcerns(): void { $compat = new Compatibility([self::lockProfile(broken: true)]); self::assertTrue($compat->usable(FakeCapability::Read)); self::assertFalse($compat->usable(FakeCapability::Write)); self::assertSame([FakeCapability::Write], array_map( static fn ($status) => $status->feature, $compat->lockOf(self::CAPS)?->locked ?? [] )); } public function testLockOnFailureWithoutProblemsIsFree(): void { $compat = new Compatibility([self::lockProfile()]); self::assertNull($compat->lockOf(self::CAPS)); self::assertSame([], $compat->problems($compat->profile('fake'))); } public function testAnInactivePluginLocksEverythingWithLockOnFailure(): void { $profile = self::lockProfile(active: static fn (): bool => false); $compat = new Compatibility([$profile]); self::assertFalse($compat->active($profile)); self::assertFalse($compat->usable(FakeCapability::Read)); self::assertSame(['Fake Plugin ist nicht installiert oder nicht aktiv.'], $compat->problems($profile)); } public function testAVersionBelowTheMinimumLocksEverything(): void { $profile = self::lockProfile(version: static fn (): string => '2.7.9', minVersion: '2.8'); $compat = new Compatibility([$profile]); self::assertSame('2.7.9', $compat->version($profile)); self::assertFalse($compat->usable(FakeCapability::Read)); self::assertFalse($compat->usable(FakeCapability::Write)); self::assertSame( ['Fake Plugin 2.7.9 ist älter als die Mindestversion 2.8.'], $compat->problems($profile) ); } public function testTheMinimumVersionItselfIsFine(): void { $profile = self::lockProfile(version: static fn (): string => '2.8', minVersion: '2.8'); self::assertTrue((new Compatibility([$profile]))->usable(FakeCapability::Read)); } public function testAnUnknownVersionWithAMinimumLocksFailClosed(): void { $profile = self::lockProfile(version: static fn (): ?string => null, minVersion: '2.8'); self::assertFalse((new Compatibility([$profile]))->usable(FakeCapability::Read)); } public function testAThrowingActiveOrVersionClosureCountsAsInactiveAndUnknown(): void { $boom = static function (): never { throw new RuntimeException('boom'); }; $profile = self::lockProfile(active: $boom, version: $boom); $compat = new Compatibility([$profile]); self::assertFalse($compat->active($profile)); self::assertNull($compat->version($profile)); self::assertFalse($compat->usable(FakeCapability::Read)); } public function testAThrowingCheckLocksEverythingFailClosed(): void { $profile = self::lockProfile(); $compat = new Compatibility([$profile], static function (): never { throw new RuntimeException('boom'); }); self::assertFalse($compat->usable(FakeCapability::Read)); self::assertFalse($compat->usable(FakeCapability::Write)); self::assertSame( ['Die Kompatibilitätsprüfung von Fake Plugin ist fehlgeschlagen.'], $compat->problems($profile) ); } public function testReleasePerVersionTakesItsMatrixFromTheClosure(): void { $profile = new Profile( 'fake', 'Fake', Tier::ReleasePerVersion, self::CAPS, matrix: static fn (): CompatibilityMatrix => CompatibilityMatrix::allIncompatible(self::CAPS, 'gesperrt') ); $compat = new Compatibility([$profile]); self::assertFalse($compat->usable(FakeCapability::Read)); self::assertSame(['gesperrt'], $compat->problems($profile)); } public function testMatrixAndCheckAreComputedOncePerProfile(): void { $checks = 0; $matrices = 0; $lock = self::lockProfile(); $release = new Profile( 'release', 'Release', Tier::ReleasePerVersion, [FakeCapability::Other], matrix: static function () use (&$matrices): CompatibilityMatrix { $matrices++; return CompatibilityMatrix::allReleased([FakeCapability::Other]); } ); $count = static function (Profile $profile) use (&$checks): ManifestCheck { $checks++; return new ManifestCheck($profile->manifests, signatures: false); }; $compat = new Compatibility([$lock, $release], $count); foreach ([1, 2, 3] as $ignored) { $compat->usable(FakeCapability::Read); $compat->usable(FakeCapability::Other); $compat->problems($lock); } self::assertSame(1, $checks); self::assertSame(1, $matrices); } public function testLookups(): void { $lock = self::lockProfile(); $other = new Profile('other', 'Other', Tier::ReportOnly, [FakeCapability::Other]); $compat = new Compatibility([$lock, $other]); self::assertSame([$lock, $other], $compat->profiles()); self::assertSame($other, $compat->profile('other')); self::assertNull($compat->profile('nope')); self::assertSame(FakeCapability::Other, $compat->capability('fake-other')); self::assertNull($compat->capability('fake-nope')); self::assertSame($other, $compat->owner(FakeCapability::Other)); self::assertSame($lock, $compat->owner(FakeCapability::Read)); } public function testAnIdTwoProfilesClaimIsAProgrammingError(): void { $this->expectException(LogicException::class); $this->expectExceptionMessage('fake-read'); new Compatibility([ self::lockProfile(), new Profile('other', 'Other', Tier::ReportOnly, [FakeCapability::Read]), ]); } public function testACapabilityNoProfileOwnsIsNotUsable(): void { self::assertFalse((new Compatibility([]))->usable(FakeCapability::Read)); } }