ReleaseMatrices::uniform(ReleaseState::Untested)); } public function testTheTestCaseDefaultReleasesEveryFeature(): void { self::assertSame([], ReleaseGate::status()->matrix->unusable()); ReleaseGate::assertFeatures(DiviFeature::cases(), 7); } public function testLockOfIsNullForUsableFeaturesAndNamesTheUnusableOnesOtherwise(): void { self::assertNull(ReleaseGate::lockOf([])); self::assertNull(ReleaseGate::lockOf([DiviFeature::PageWrite])); DiviFeatureFixtures::lock(DiviFeature::Catalog); $lock = ReleaseGate::lockOf([DiviFeature::PageWrite, DiviFeature::Catalog, DiviFeature::Catalog]); self::assertNotNull($lock); self::assertSame([DiviFeature::Catalog], $lock->features); self::assertSame('5.3.0', $lock->status->version); } public function testALockWhoseLockedCapabilityIsNoDiviFeatureStillRefusesTheFeatureAsked(): void { $foreign = new class () implements Capability { public function id(): string { return DiviFeature::PageWrite->id(); } public function label(): string { return 'fremde Fähigkeit'; } public function isWrite(): bool { return true; } }; ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseStatus::of( '5.3.0', CompatibilityMatrix::uniform([$foreign], ReleaseState::Incompatible, ['fremde Fähigkeit']) )); try { ReleaseGate::assertFeature(DiviFeature::PageWrite); self::fail('a locked feature must refuse'); } catch (DiviLocked $locked) { self::assertSame([DiviFeature::PageWrite], $locked->features); } } public function testAssertFeatureRefusesOnlyALockedFeature(): void { ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status( [DiviFeature::PresetWrite], ReleaseState::Incompatible )); ReleaseGate::assertFeature(DiviFeature::PageWrite); try { ReleaseGate::assertFeature(DiviFeature::PresetWrite, 7); self::fail('a locked feature must refuse'); } catch (DiviLocked $locked) { self::assertSame([DiviFeature::PresetWrite], $locked->features); self::assertToolError($locked->toError(), 'divi_incompatible'); } } public function testAssertFeaturesNamesEveryLockedFeatureOnce(): void { ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status( [DiviFeature::LibraryWrite, DiviFeature::ThemeBuilderWrite] )); try { ReleaseGate::assertFeatures([ DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite, DiviFeature::LibraryWrite, DiviFeature::ThemeBuilderWrite, ]); self::fail('locked features must refuse'); } catch (DiviLocked $locked) { self::assertSame([DiviFeature::ThemeBuilderWrite, DiviFeature::LibraryWrite], $locked->features); } } public function testAssertFeatureThrowsTheLockWithTheStatus(): void { $this->untested(); try { ReleaseGate::assertFeature(DiviFeature::PageWrite, 7); self::fail('an untested Divi must lock writes'); } catch (DiviLocked $locked) { self::assertSame('5.3.0', $locked->status->version); self::assertToolError($locked->toError(), 'divi_untested'); } } public function testProbingAllowsOnlyThatPostAndOnlyMeanwhile(): void { $this->untested(); $result = ReleaseGate::whileProbing(41, static function (): string { ReleaseGate::assertFeatures(DiviFeature::cases(), 41); return 'written'; }); self::assertSame('written', $result); foreach ([static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeatures( DiviFeature::cases(), 42 )), static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeatures( DiviFeature::cases() )), static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeature( DiviFeature::PageWrite, 42 )), static fn () => ReleaseGate::assertFeature(DiviFeature::PageWrite, 41)] as $attempt) { try { $attempt(); self::fail('only post 41, only while probing'); } catch (DiviLocked) { self::addToAssertionCount(1); } } } public function testProbingEndsEvenWhenTheWriteThrows(): void { $this->untested(); try { ReleaseGate::whileProbing(41, static function (): never { throw new RuntimeException('boom'); }); } catch (RuntimeException) { } $this->expectException(DiviLocked::class); ReleaseGate::assertFeature(DiviFeature::PageWrite, 41); } public function testStatusIsComputedOncePerRequestUntilForgotten(): void { $calls = 0; ReleaseGate::useResolver(static function () use (&$calls): ReleaseStatus { ++$calls; return ReleaseMatrices::released('5.2.1'); }); ReleaseGate::status(); ReleaseGate::status(); self::assertSame(1, $calls); ReleaseGate::forget(); ReleaseGate::status(); self::assertSame(2, $calls); } public function testInvalidateForgetsTheStatusAndClearsTheMirrorCacheOnlyWhenAsked(): void { $calls = 0; $deleted = []; ReleaseGate::useResolver(static function () use (&$calls): ReleaseStatus { ++$calls; return ReleaseMatrices::released('5.2.1'); }); Functions\when('delete_transient')->alias(static function (string $key) use (&$deleted): bool { $deleted[] = $key; return true; }); ReleaseGate::status(); ReleaseGate::invalidate(); ReleaseGate::status(); self::assertSame(2, $calls); self::assertSame([], $deleted, 'a release or withdrawal leaves the mirror results alone'); ReleaseGate::invalidate(mirrors: true); ReleaseGate::status(); self::assertSame(3, $calls); self::assertSame([ReleaseResolver::CACHE], $deleted); } public function testAFailingResolverLocksWrites(): void { ReleaseGate::useResolver(static function (): never { throw new RuntimeException('theme API gone'); }); $log = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log'); $previous = ini_set('error_log', $log); try { $status = ReleaseGate::status(); } finally { ini_set('error_log', (string) $previous); } self::assertSame(ReleaseState::Incompatible, $status->state); self::assertCount(count(DiviFeature::cases()), $status->matrix->unusable(), 'every feature is locked'); self::assertSame( ['Der Freigabe-Zustand ließ sich nicht ermitteln (Details im PHP-Error-Log).'], $status->matrix->of(DiviFeature::PageRead)->problems ); self::assertStringContainsString('theme API gone', (string) file_get_contents($log)); unlink($log); $this->expectException(DiviLocked::class); ReleaseGate::assertFeature(DiviFeature::PageRead); } public function testFindsTheLockInsideAWrappedFailure(): void { $locked = new DiviLocked(ReleaseMatrices::uniform(ReleaseState::Untested, '5.3.0'), [DiviFeature::PageWrite]); $wrapped = WriteFailed::rolledBack('Nicht gespeichert.', new RuntimeException('draft', 0, $locked)); self::assertSame($locked, DiviLocked::in($wrapped)); self::assertNull(DiviLocked::in(new RuntimeException('other'))); } }