From cf3c5dfe76a268c857bb00a4c20ab3d30626df32 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Wed, 30 Sep 2026 01:46:05 +0200 Subject: [PATCH] feat: Divi release status, store, resolver and gate Co-Authored-By: Claude Opus 5.5 (1M context) --- src/Integrations/Divi/Release/DiviLocked.php | 41 ++++++ .../Divi/Release/DiviReleases.php | 16 +++ .../Divi/Release/ManifestCheck.php | 4 +- src/Integrations/Divi/Release/ReleaseGate.php | 87 ++++++++++++ .../Divi/Release/ReleaseResolver.php | 86 ++++++++++++ .../Divi/Release/ReleaseState.php | 15 +++ .../Divi/Release/ReleaseStatus.php | 108 +++++++++++++++ .../Divi/Release/ReleaseStore.php | 99 ++++++++++++++ .../Divi/Release/ReleaseGateTest.php | 126 ++++++++++++++++++ .../Divi/Release/ReleaseResolverTest.php | 107 +++++++++++++++ .../Divi/Release/ReleaseStatusTest.php | 86 ++++++++++++ .../Divi/Release/ReleaseStoreTest.php | 54 ++++++++ tests/Unit/TestCase.php | 6 + tests/bootstrap.php | 4 + 14 files changed, 838 insertions(+), 1 deletion(-) create mode 100644 src/Integrations/Divi/Release/DiviLocked.php create mode 100644 src/Integrations/Divi/Release/DiviReleases.php create mode 100644 src/Integrations/Divi/Release/ReleaseGate.php create mode 100644 src/Integrations/Divi/Release/ReleaseResolver.php create mode 100644 src/Integrations/Divi/Release/ReleaseState.php create mode 100644 src/Integrations/Divi/Release/ReleaseStatus.php create mode 100644 src/Integrations/Divi/Release/ReleaseStore.php create mode 100644 tests/Unit/Integrations/Divi/Release/ReleaseGateTest.php create mode 100644 tests/Unit/Integrations/Divi/Release/ReleaseResolverTest.php create mode 100644 tests/Unit/Integrations/Divi/Release/ReleaseStatusTest.php create mode 100644 tests/Unit/Integrations/Divi/Release/ReleaseStoreTest.php diff --git a/src/Integrations/Divi/Release/DiviLocked.php b/src/Integrations/Divi/Release/DiviLocked.php new file mode 100644 index 0000000..3e5ae1d --- /dev/null +++ b/src/Integrations/Divi/Release/DiviLocked.php @@ -0,0 +1,41 @@ +versionLabel(), + $status->state->value + )); + } + + public function toError(): WP_Error + { + return $this->status->refusal(); + } + + /** The lock anywhere in $e's chain of previous exceptions (callers wrap failures in WriteFailed). */ + public static function in(Throwable $e): ?self + { + for ($cause = $e; $cause !== null; $cause = $cause->getPrevious()) { + if ($cause instanceof self) { + return $cause; + } + } + + return null; + } +} diff --git a/src/Integrations/Divi/Release/DiviReleases.php b/src/Integrations/Divi/Release/DiviReleases.php new file mode 100644 index 0000000..25debcd --- /dev/null +++ b/src/Integrations/Divi/Release/DiviReleases.php @@ -0,0 +1,16 @@ + check. Code checks (calls with the argument count * the adapter passes, constants) and mirror checks decide the release gate; registration checks (post types, * taxonomies, block types) depend on the request context (stage3 finding 6) and only show in reports. + * + * Not final so the resolver can be tested with a mock. */ -final class ManifestCheck +class ManifestCheck { /** @var array */ private array $manifests; diff --git a/src/Integrations/Divi/Release/ReleaseGate.php b/src/Integrations/Divi/Release/ReleaseGate.php new file mode 100644 index 0000000..3d21383 --- /dev/null +++ b/src/Integrations/Divi/Release/ReleaseGate.php @@ -0,0 +1,87 @@ + (new ReleaseResolver())->resolve())(); + } catch (Throwable $e) { + Log::failure('divi release status', $e); + self::$status = new ReleaseStatus( + ReleaseState::Incompatible, + '', + null, + ['Der Freigabe-Zustand ließ sich nicht ermitteln (Details im PHP-Error-Log).'] + ); + } + + return self::$status; + } + + /** @param (Closure(): ReleaseStatus)|null $resolver null = ReleaseResolver; tests set their own. */ + public static function useResolver(?Closure $resolver): void + { + self::$resolver = $resolver; + self::$status = null; + self::$probe = null; + } + + /** The next status() computes again (after a check, a release or a withdrawal). */ + public static function forget(): void + { + self::$status = null; + } + + /** @throws DiviLocked */ + public static function assertWritable(?int $postId = null): void + { + if (self::$probe !== null && $postId === self::$probe) { + return; + } + + $status = self::status(); + if (! $status->writable()) { + throw new DiviLocked($status); + } + } + + public static function whileProbing(int $postId, callable $write): mixed + { + $previous = self::$probe; + self::$probe = $postId; + + try { + return $write(); + } finally { + self::$probe = $previous; + } + } +} diff --git a/src/Integrations/Divi/Release/ReleaseResolver.php b/src/Integrations/Divi/Release/ReleaseResolver.php new file mode 100644 index 0000000..02b82f0 --- /dev/null +++ b/src/Integrations/Divi/Release/ReleaseResolver.php @@ -0,0 +1,86 @@ +version ?? DiviEnvironment::version(...))() ?? ''; + $check = $this->check ?? new ManifestCheck(); + $problems = array_merge( + ManifestCheck::failed($check->codeChecks()), + $this->mirrorProblems($check, $version) + ); + + if ($problems !== []) { + return new ReleaseStatus(ReleaseState::Incompatible, $version, null, $problems); + } + if (in_array($version, DiviReleases::TESTED, true)) { + return ReleaseStatus::released($version); + } + $entry = ($this->store ?? new ReleaseStore())->find($version, Plugin::VERSION); + + return $entry !== null + ? new ReleaseStatus(ReleaseState::Released, $version, ReleaseStatus::SOURCE_DATABASE, [], $entry) + : new ReleaseStatus(ReleaseState::Untested, $version); + } + + /** What the cached mirror results and a check report are valid for. */ + public static function stateKey(string $version, ?string $themeDir = null): string + { + $themeDir ??= DiviEnvironment::themeDir(); + $parts = [ + $version, + Plugin::VERSION, + is_readable(DiviReference::FILE) ? (string) md5_file(DiviReference::FILE) : '', + ]; + foreach (DiviAdapters::mirrors() as $mirror) { + $file = $themeDir === null ? '' : $themeDir . '/' . $mirror->file; + $parts[] = $mirror->file . '@' . ($file !== '' && is_file($file) ? (string) filemtime($file) : '-'); + } + + return hash('sha256', implode('|', $parts)); + } + + /** @return list */ + private function mirrorProblems(ManifestCheck $check, string $version): array + { + $key = ($this->stateKey ?? self::stateKey(...))($version); + $cached = get_transient(self::CACHE); + if (is_array($cached) && ($cached['key'] ?? null) === $key && is_array($cached['problems'] ?? null)) { + return array_values(array_filter($cached['problems'], 'is_string')); + } + + $problems = ManifestCheck::failed($check->mirrorChecks()); + set_transient(self::CACHE, ['key' => $key, 'problems' => $problems], DAY_IN_SECONDS); + + return $problems; + } +} diff --git a/src/Integrations/Divi/Release/ReleaseState.php b/src/Integrations/Divi/Release/ReleaseState.php new file mode 100644 index 0000000..fb4ecea --- /dev/null +++ b/src/Integrations/Divi/Release/ReleaseState.php @@ -0,0 +1,15 @@ + $problems Failed code and mirror checks (ManifestCheck labels). + * @param array|null $release The database entry of a SOURCE_DATABASE release. + */ + public function __construct( + public readonly ReleaseState $state, + public readonly string $version, + public readonly ?string $source = null, + public readonly array $problems = [], + public readonly ?array $release = null + ) { + } + + public static function released(string $version, string $source = self::SOURCE_CODE): self + { + return new self(ReleaseState::Released, $version, $source); + } + + public function writable(): bool + { + return $this->state === ReleaseState::Released; + } + + public function versionLabel(): string + { + return $this->version === '' ? 'Divi (Version unbekannt)' : 'Divi ' . $this->version; + } + + /** The error of a refused write. @throws LogicException For a released status. */ + public function refusal(): WP_Error + { + return match ($this->state) { + ReleaseState::Untested => Errors::make( + 'divi_untested', + sprintf(self::UNTESTED, $this->versionLabel()), + self::UNTESTED_HINT, + 423 + ), + ReleaseState::Incompatible => Errors::make( + 'divi_incompatible', + sprintf(self::INCOMPATIBLE, $this->versionLabel()), + self::INCOMPATIBLE_HINT, + 423, + ['problems' => $this->problems] + ), + ReleaseState::Released => throw new LogicException('a released Divi refuses nothing'), + }; + } + + /** The warning a reading Divi tool carries while writes are locked. */ + public function notice(): string + { + return sprintf( + self::NOTICE, + $this->versionLabel(), + $this->state === ReleaseState::Incompatible ? 'inkompatibel' : 'ungetestet' + ); + } + + /** + * @return array{state: string, version: string, source: ?string, released_at: ?string, released_by: ?int, + * problems: list} + */ + public function toArray(): array + { + return [ + 'state' => $this->state->value, + 'version' => $this->version, + 'source' => $this->source, + 'released_at' => is_string($this->release['released_at'] ?? null) + ? $this->release['released_at'] + : null, + 'released_by' => is_int($this->release['user_id'] ?? null) ? $this->release['user_id'] : null, + 'problems' => $this->problems, + ]; + } +} diff --git a/src/Integrations/Divi/Release/ReleaseStore.php b/src/Integrations/Divi/Release/ReleaseStore.php new file mode 100644 index 0000000..66187f3 --- /dev/null +++ b/src/Integrations/Divi/Release/ReleaseStore.php @@ -0,0 +1,99 @@ + + */ + public function entries(): array + { + $raw = get_option(self::OPTION, []); + $entries = []; + foreach (is_array($raw) ? $raw : [] as $row) { + if ( + is_array($row) + && is_string($row['divi_version'] ?? null) + && is_string($row['plugin_version'] ?? null) + && is_int($row['user_id'] ?? null) + && is_string($row['released_at'] ?? null) + && is_string($row['report_id'] ?? null) + ) { + $entries[] = [ + 'divi_version' => $row['divi_version'], + 'plugin_version' => $row['plugin_version'], + 'user_id' => $row['user_id'], + 'released_at' => $row['released_at'], + 'report_id' => $row['report_id'], + ]; + } + } + + return $entries; + } + + /** + * @return array{divi_version: string, plugin_version: string, user_id: int, released_at: string, + * report_id: string}|null + */ + public function find(string $diviVersion, string $pluginVersion): ?array + { + foreach ($this->entries() as $entry) { + if ($entry['divi_version'] === $diviVersion && $entry['plugin_version'] === $pluginVersion) { + return $entry; + } + } + + return null; + } + + public function add(string $diviVersion, string $pluginVersion, int $userId, string $reportId): void + { + $entries = $this->without($diviVersion, $pluginVersion); + $entries[] = [ + 'divi_version' => $diviVersion, + 'plugin_version' => $pluginVersion, + 'user_id' => $userId, + 'released_at' => gmdate('c'), + 'report_id' => $reportId, + ]; + update_option(self::OPTION, $entries, false); + } + + public function withdraw(string $diviVersion, string $pluginVersion): bool + { + if ($this->find($diviVersion, $pluginVersion) === null) { + return false; + } + update_option(self::OPTION, $this->without($diviVersion, $pluginVersion), false); + + return true; + } + + /** + * @return list + */ + private function without(string $diviVersion, string $pluginVersion): array + { + return array_values(array_filter( + $this->entries(), + static fn (array $entry): bool => $entry['divi_version'] !== $diviVersion + || $entry['plugin_version'] !== $pluginVersion + )); + } +} diff --git a/tests/Unit/Integrations/Divi/Release/ReleaseGateTest.php b/tests/Unit/Integrations/Divi/Release/ReleaseGateTest.php new file mode 100644 index 0000000..7cc7a42 --- /dev/null +++ b/tests/Unit/Integrations/Divi/Release/ReleaseGateTest.php @@ -0,0 +1,126 @@ + new ReleaseStatus(ReleaseState::Untested, '5.3.0')); + } + + public function testTheTestCaseDefaultIsReleased(): void + { + self::assertTrue(ReleaseGate::status()->writable()); + ReleaseGate::assertWritable(7); + } + + public function testAssertWritableThrowsTheLockWithTheStatus(): void + { + $this->untested(); + + try { + ReleaseGate::assertWritable(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::assertWritable(41); + + return 'written'; + }); + self::assertSame('written', $result); + + foreach ([static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable(42)), + static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable()), + static fn () => ReleaseGate::assertWritable(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::assertWritable(41); + } + + public function testStatusIsComputedOncePerRequestUntilForgotten(): void + { + $calls = 0; + ReleaseGate::useResolver(static function () use (&$calls): ReleaseStatus { + ++$calls; + + return ReleaseStatus::released('5.2.1'); + }); + + ReleaseGate::status(); + ReleaseGate::status(); + self::assertSame(1, $calls); + + ReleaseGate::forget(); + ReleaseGate::status(); + self::assertSame(2, $calls); + } + + 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::assertStringContainsString('theme API gone', (string) file_get_contents($log)); + unlink($log); + $this->expectException(DiviLocked::class); + ReleaseGate::assertWritable(); + } + + public function testFindsTheLockInsideAWrappedFailure(): void + { + $locked = new DiviLocked(new ReleaseStatus(ReleaseState::Untested, '5.3.0')); + $wrapped = WriteFailed::rolledBack('Nicht gespeichert.', new RuntimeException('draft', 0, $locked)); + + self::assertSame($locked, DiviLocked::in($wrapped)); + self::assertNull(DiviLocked::in(new RuntimeException('other'))); + } +} diff --git a/tests/Unit/Integrations/Divi/Release/ReleaseResolverTest.php b/tests/Unit/Integrations/Divi/Release/ReleaseResolverTest.php new file mode 100644 index 0000000..9df4bcc --- /dev/null +++ b/tests/Unit/Integrations/Divi/Release/ReleaseResolverTest.php @@ -0,0 +1,107 @@ + */ + private array $transients = []; + + protected function setUp(): void + { + parent::setUp(); + $this->transients = []; + Functions\when('get_transient')->alias(fn (string $key): mixed => $this->transients[$key] ?? false); + Functions\when('set_transient')->alias(function (string $key, mixed $value): bool { + $this->transients[$key] = $value; + + return true; + }); + } + + /** + * @param array $code + * @param array $mirrors + */ + private function resolver( + string $version, + array $code = [], + array $mirrors = [], + ?array $entry = null + ): ReleaseResolver { + $check = Mockery::mock(ManifestCheck::class); + $check->allows('codeChecks')->andReturn($code); + $check->allows('mirrorChecks')->andReturn($mirrors); + $store = Mockery::mock(ReleaseStore::class); + $store->allows('find')->with($version, Plugin::VERSION)->andReturn($entry); + + return new ReleaseResolver( + $check, + $store, + static fn (): ?string => $version, + static fn (string $v): string => 'key-' . $v + ); + } + + public function testATestedVersionWithGreenChecksIsReleasedFromCode(): void + { + $status = $this->resolver('5.2.1')->resolve(); + + self::assertSame(ReleaseState::Released, $status->state); + self::assertSame('code', $status->source); + } + + public function testAnUnknownVersionIsUntestedUnlessTheDatabaseReleasedIt(): void + { + self::assertSame(ReleaseState::Untested, $this->resolver('5.3.0')->resolve()->state); + + $released = $this->resolver('5.3.0', entry: ['user_id' => 1, 'released_at' => 'd'])->resolve(); + self::assertSame(ReleaseState::Released, $released->state); + self::assertSame('database', $released->source); + } + + public function testAFailedCheckMakesEvenATestedVersionIncompatible(): void + { + $status = $this->resolver('5.2.1', ['Seiten: et_x()' => static fn (): bool => false])->resolve(); + + self::assertSame(ReleaseState::Incompatible, $status->state); + self::assertSame(['Seiten: et_x()'], $status->problems); + } + + public function testMirrorResultsAreCachedByStateKey(): void + { + $calls = 0; + $mirrors = ['Theme Builder: Nachbau von y()' => static function () use (&$calls): bool { + ++$calls; + + return false; + }]; + + $first = $this->resolver('5.2.1', mirrors: $mirrors)->resolve(); + $second = $this->resolver('5.2.1', mirrors: $mirrors)->resolve(); + + self::assertSame(1, $calls, 'the second resolve reads the transient'); + self::assertSame(ReleaseState::Incompatible, $second->state); + self::assertSame($first->problems, $second->problems); + self::assertSame('key-5.2.1', $this->transients[ReleaseResolver::CACHE]['key']); + + $this->resolver('5.2.2', mirrors: $mirrors)->resolve(); + self::assertSame(2, $calls, 'another state key checks again'); + } + + public function testWithoutDiviTheVersionIsEmptyAndUntested(): void + { + self::assertSame(ReleaseState::Untested, $this->resolver('')->resolve()->state); + } +} diff --git a/tests/Unit/Integrations/Divi/Release/ReleaseStatusTest.php b/tests/Unit/Integrations/Divi/Release/ReleaseStatusTest.php new file mode 100644 index 0000000..0a5c83e --- /dev/null +++ b/tests/Unit/Integrations/Divi/Release/ReleaseStatusTest.php @@ -0,0 +1,86 @@ +writable()); + self::assertFalse((new ReleaseStatus(ReleaseState::Untested, '5.3.0'))->writable()); + self::assertFalse((new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, ['x']))->writable()); + } + + public function testUntestedRefusalNamesTheVersionAndTheWayOut(): void + { + $payload = self::assertToolError( + (new ReleaseStatus(ReleaseState::Untested, '5.3.0'))->refusal(), + 'divi_untested' + ); + + self::assertSame( + 'Achtung: Divi 5.3.0 ist auf dieser Website noch nicht für LIT MCP freigegeben. Schreibende ' + . 'Divi-Werkzeuge sind gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Divi die ' + . 'Prüfung ausführt und die Version freigibt. Es wurde nichts geändert.', + $payload['message'] + ); + self::assertStringContainsString('lesende Divi-Werkzeuge funktionieren weiter', $payload['hint']); + } + + public function testIncompatibleRefusalListsTheProblems(): void + { + $status = new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, ['Theme Builder: Nachbau von x()']); + $payload = self::assertToolError($status->refusal(), 'divi_incompatible'); + + self::assertSame( + 'Divi 5.3.0 hat Stellen geändert, die LIT MCP nutzt. Schreibende Divi-Werkzeuge sind gesperrt, bis ' + . 'LIT MCP aktualisiert wurde. Es wurde nichts geändert.', + $payload['message'] + ); + self::assertSame(['Theme Builder: Nachbau von x()'], $payload['problems']); + } + + public function testNoticesForReadingTools(): void + { + self::assertSame( + 'Achtung: Divi 5.3.0 ist für LIT MCP ungetestet; Lesen funktioniert, Schreiben ist gesperrt.', + (new ReleaseStatus(ReleaseState::Untested, '5.3.0'))->notice() + ); + self::assertSame( + 'Achtung: Divi (Version unbekannt) ist für LIT MCP inkompatibel; Lesen funktioniert, Schreiben ist ' + . 'gesperrt.', + (new ReleaseStatus(ReleaseState::Incompatible, ''))->notice() + ); + } + + public function testAReleasedStatusHasNoRefusal(): void + { + $this->expectException(LogicException::class); + + ReleaseStatus::released('5.2.1')->refusal(); + } + + public function testToArrayForSiteStatus(): void + { + $status = new ReleaseStatus( + ReleaseState::Released, + '5.3.0', + ReleaseStatus::SOURCE_DATABASE, + [], + ['released_at' => '2026-10-01T08:00:00+00:00', 'user_id' => 1] + ); + + self::assertSame( + ['state' => 'released', 'version' => '5.3.0', 'source' => 'database', + 'released_at' => '2026-10-01T08:00:00+00:00', 'released_by' => 1, 'problems' => []], + $status->toArray() + ); + } +} diff --git a/tests/Unit/Integrations/Divi/Release/ReleaseStoreTest.php b/tests/Unit/Integrations/Divi/Release/ReleaseStoreTest.php new file mode 100644 index 0000000..7972b54 --- /dev/null +++ b/tests/Unit/Integrations/Divi/Release/ReleaseStoreTest.php @@ -0,0 +1,54 @@ + */ + private array $options = []; + + protected function setUp(): void + { + parent::setUp(); + $this->options = []; + Functions\when('get_option')->alias( + fn (string $name, mixed $default = false): mixed => $this->options[$name] ?? $default + ); + Functions\when('update_option')->alias(function (string $name, mixed $value, mixed $autoload = null): bool { + $this->options[$name] = $value; + self::assertFalse($autoload, 'releases are read only by the gate – no autoload'); + + return true; + }); + } + + public function testAddsFindsAndWithdrawsPerDiviAndPluginVersion(): void + { + $store = new ReleaseStore(); + $store->add('5.3.0', '0.6.0', 1, 'r1'); + $store->add('5.3.0', '0.6.0', 2, 'r2'); + + self::assertCount(1, $store->entries(), 'one entry per version pair'); + self::assertSame(2, $store->find('5.3.0', '0.6.0')['user_id'] ?? null); + self::assertNull($store->find('5.3.0', '0.7.0'), 'another plugin version needs its own check'); + self::assertTrue($store->withdraw('5.3.0', '0.6.0')); + self::assertFalse($store->withdraw('5.3.0', '0.6.0')); + self::assertSame([], $store->entries()); + } + + public function testIgnoresBrokenRows(): void + { + $this->options[ReleaseStore::OPTION] = ['x', ['divi_version' => '5.3.0'], [ + 'divi_version' => '5.2.2', 'plugin_version' => '0.6.0', 'user_id' => 3, + 'released_at' => 'd', 'report_id' => 'r', + ]]; + + self::assertSame(['5.2.2'], array_column((new ReleaseStore())->entries(), 'divi_version')); + } +} diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index 7fa1e2c..c43241a 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -6,6 +6,8 @@ namespace LIT\MCP\Tests\Unit; use Brain\Monkey; use Brain\Monkey\Functions; +use LIT\MCP\Integrations\Divi\Release\ReleaseGate; +use LIT\MCP\Integrations\Divi\Release\ReleaseStatus; use LIT\MCP\Tests\Support\AssertsToolErrors; use LIT\MCP\Tests\Support\FakeChangeLog; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; @@ -32,10 +34,14 @@ abstract class TestCase extends BaseTestCase 'wp_unslash' => static fn ($value) => $value, 'sanitize_text_field' => static fn ($value): string => trim(strip_tags((string) $value)), ]); + + // Divi is released unless a test says otherwise (the release gate would ask WordPress and Divi). + ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseStatus::released('5.2.1')); } protected function tearDown(): void { + ReleaseGate::useResolver(null); Monkey\tearDown(); parent::tearDown(); FakeChangeLog::verify(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index cffb39c..fa16d19 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -6,6 +6,10 @@ if (! defined('ABSPATH')) { define('ABSPATH', __DIR__ . '/'); } +if (! defined('DAY_IN_SECONDS')) { + define('DAY_IN_SECONDS', 86400); +} + require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/Stubs/WpError.php'; require_once __DIR__ . '/Stubs/WpUser.php';