SuperCacheProbe prüft beim Prüfen, ob wp_cache_post_edit die Cache-Datei einer Testseite entfernt; während der Probe ist nur der Einzelseiten-Purge erlaubt, der Pfad kommt aus home und es werden nur feste Dateinamen gelöscht. ProbeStep bündelt die Schrittlogik von Pods und WP Super Cache; ThrowawayPost hält Marker und Titel zentral und räumt auch private Probe-Seiten auf (WP Super Cache wie Pods beim Laden des Reiters). super-cache-smoke.sh prüft gegen das echte Plugin; lib.sh sperrt parallele Smoke-Läufe per flock, wp-config.php wird atomar wiederhergestellt. Doku: Host-Abhängigkeit des Einzel-Purges, WP-Cron für den Nachlauf, Komplett-Purge durch Divi beim Prüfen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
526 lines
20 KiB
PHP
526 lines
20 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\SuperCache;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use Closure;
|
|
use LIT\MCP\Compatibility\ThrowawayPost;
|
|
use LIT\MCP\Integrations\SuperCache\SuperCacheProbe;
|
|
use LIT\MCP\Tests\Unit\TestCase;
|
|
use Mockery;
|
|
use RuntimeException;
|
|
use WP_Post;
|
|
|
|
final class SuperCacheProbeTest extends TestCase
|
|
{
|
|
private string $cacheRoot = '';
|
|
|
|
/** @var list<int> */
|
|
private array $deleted = [];
|
|
|
|
/** @var list<string> "<id>:<status>" in call order. */
|
|
private array $statuses = [];
|
|
|
|
/** @var array<string, mixed>|null The wp_insert_post arguments. */
|
|
private ?array $inserted = null;
|
|
|
|
/** @var array<string, mixed> Globals the test changes (only those that existed). */
|
|
private array $savedGlobals = [];
|
|
|
|
private const GLOBALS_CHANGED = ['cache_enabled', 'cache_path', 'wpdb', 'wp_cache_clear_on_post_edit'];
|
|
|
|
/** The status the fake status switch refuses to set (returns false); null = none. */
|
|
private ?string $refusedStatus = null;
|
|
|
|
/** The status the fake status switch throws for; null = none. */
|
|
private ?string $throwingStatus = null;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->cacheRoot = sys_get_temp_dir() . '/lit-mcp-wpsc-' . bin2hex(random_bytes(4)) . '/';
|
|
mkdir($this->cacheRoot . 'supercache/example.test/', 0777, true);
|
|
$this->deleted = [];
|
|
$this->statuses = [];
|
|
$this->inserted = null;
|
|
$this->refusedStatus = null;
|
|
$this->throwingStatus = null;
|
|
$this->savedGlobals = [];
|
|
foreach (self::GLOBALS_CHANGED as $name) {
|
|
if (array_key_exists($name, $GLOBALS)) {
|
|
$this->savedGlobals[$name] = $GLOBALS[$name];
|
|
}
|
|
}
|
|
Functions\when('wp_insert_post')->alias(function (array $post): int {
|
|
$this->inserted = $post;
|
|
|
|
return 77;
|
|
});
|
|
Functions\when('wp_delete_post')->alias(function (int $id, bool $force): WP_Post {
|
|
self::assertTrue($force, 'never to the trash');
|
|
$this->deleted[] = $id;
|
|
|
|
return new WP_Post(['ID' => $id]);
|
|
});
|
|
Functions\when('wp_mkdir_p')->alias(static fn (string $dir): bool => is_dir($dir) || @mkdir($dir, 0777, true));
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
foreach (self::GLOBALS_CHANGED as $name) {
|
|
if (array_key_exists($name, $this->savedGlobals)) {
|
|
$GLOBALS[$name] = $this->savedGlobals[$name];
|
|
} else {
|
|
unset($GLOBALS[$name]);
|
|
}
|
|
}
|
|
self::removeTree($this->cacheRoot);
|
|
parent::tearDown();
|
|
}
|
|
|
|
private static function removeTree(string $path): void
|
|
{
|
|
foreach (glob(rtrim($path, '/') . '/{,.}*', GLOB_BRACE) ?: [] as $entry) {
|
|
if (in_array(basename($entry), ['.', '..'], true)) {
|
|
continue;
|
|
}
|
|
is_dir($entry) ? self::removeTree($entry) : unlink($entry);
|
|
}
|
|
@rmdir($path);
|
|
}
|
|
|
|
private function pageDir(): string
|
|
{
|
|
return $this->cacheRoot . 'supercache/example.test/lit-mcp-check-77/';
|
|
}
|
|
|
|
/** @param (Closure(int): void)|null $purge */
|
|
private function probe(?Closure $purge = null, ?Closure $cacheDir = null, bool $enabled = true): SuperCacheProbe
|
|
{
|
|
return new SuperCacheProbe(
|
|
static fn (): bool => $enabled,
|
|
$cacheDir ?? fn (int $id): string => $this->pageDir(),
|
|
$purge ?? fn (int $id) => unlink($this->pageDir() . 'index.html'),
|
|
function (int $id, string $status): bool {
|
|
$this->statuses[] = $id . ':' . $status;
|
|
if ($status === $this->throwingStatus) {
|
|
throw new RuntimeException('Status kaputt');
|
|
}
|
|
|
|
return $status !== $this->refusedStatus;
|
|
}
|
|
);
|
|
}
|
|
|
|
/** Purge fake that does what WPSC does: the page's cache file disappears. */
|
|
private function purgingProbe(): SuperCacheProbe
|
|
{
|
|
return $this->probe(function (int $id): void {
|
|
self::assertFileExists($this->pageDir() . 'index.html', 'the file is there before the purge');
|
|
unlink($this->pageDir() . 'index.html');
|
|
});
|
|
}
|
|
|
|
/** Captures what Log writes to the PHP error log while $run runs. */
|
|
private function logged(Closure $run): string
|
|
{
|
|
$log = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
|
|
$previous = ini_set('error_log', $log);
|
|
try {
|
|
$run();
|
|
|
|
return (string) file_get_contents($log);
|
|
} finally {
|
|
ini_set('error_log', (string) $previous);
|
|
unlink($log);
|
|
}
|
|
}
|
|
|
|
public function testGreenWhenThePurgeRemovesTheCacheFileAndNothingIsLeftBehind(): void
|
|
{
|
|
self::assertSame([], $this->purgingProbe()->run());
|
|
|
|
self::assertDirectoryDoesNotExist($this->pageDir());
|
|
self::assertSame([77], $this->deleted);
|
|
}
|
|
|
|
public function testCreatesAMarkedDraftPageAndOnlyPassesThroughPrivateForThePurge(): void
|
|
{
|
|
$this->purgingProbe()->run();
|
|
|
|
self::assertSame('page', $this->inserted['post_type']);
|
|
self::assertSame('draft', $this->inserted['post_status']);
|
|
self::assertStringStartsWith(ThrowawayPost::TITLE, $this->inserted['post_title']);
|
|
self::assertSame([ThrowawayPost::MARKER => '1'], $this->inserted['meta_input']);
|
|
self::assertSame(['77:private', '77:draft'], $this->statuses);
|
|
}
|
|
|
|
public function testACacheFileThatSurvivesThePurgeIsAFixedTextAndIsRemovedAnyway(): void
|
|
{
|
|
$problems = [];
|
|
$logged = $this->logged(function () use (&$problems): void {
|
|
$problems = $this->probe(static fn (int $id) => null)->run();
|
|
});
|
|
|
|
self::assertSame(['Die Testdatei im Cache blieb nach dem Leeren durch WP Super Cache bestehen.'], $problems);
|
|
self::assertStringContainsString('lit-mcp-check-77', $logged);
|
|
self::assertDirectoryDoesNotExist($this->pageDir());
|
|
self::assertSame([77], $this->deleted);
|
|
}
|
|
|
|
public function testARenamedRebuildFileCountsAsPurgedAndIsCleanedUp(): void
|
|
{
|
|
$problems = $this->probe(function (int $id): void {
|
|
rename($this->pageDir() . 'index.html', $this->pageDir() . 'index.html.needs-rebuild');
|
|
})->run();
|
|
|
|
self::assertSame([], $problems);
|
|
self::assertDirectoryDoesNotExist($this->pageDir());
|
|
}
|
|
|
|
public function testADirectoryThePurgeAlreadyRemovedIsNoProblem(): void
|
|
{
|
|
$problems = $this->probe(function (int $id): void {
|
|
unlink($this->pageDir() . 'index.html');
|
|
rmdir($this->pageDir());
|
|
})->run();
|
|
|
|
self::assertSame([], $problems);
|
|
}
|
|
|
|
public function testAnExceptionOfThePurgeIsAFixedTextALogLineAndEverythingIsCleanedUp(): void
|
|
{
|
|
$problems = [];
|
|
$logged = $this->logged(function () use (&$problems): void {
|
|
$problems = $this->probe(static function (int $id): never {
|
|
throw new RuntimeException('WPSC kaputt');
|
|
})->run();
|
|
});
|
|
|
|
self::assertSame(['Super-Cache-Probe abgebrochen (Details im PHP-Error-Log).'], $problems);
|
|
self::assertStringContainsString('WPSC kaputt', $logged);
|
|
self::assertDirectoryDoesNotExist($this->pageDir());
|
|
self::assertSame([77], $this->deleted);
|
|
self::assertSame(['77:private', '77:draft'], $this->statuses);
|
|
}
|
|
|
|
public function testNothingToProbeWhileTheCacheIsSwitchedOff(): void
|
|
{
|
|
self::assertNull($this->probe(null, null, false)->run());
|
|
self::assertNull($this->inserted);
|
|
}
|
|
|
|
public function testNothingToProbeWithoutACachePathForThePage(): void
|
|
{
|
|
self::assertNull($this->probe(null, static fn (int $id): ?string => null)->run());
|
|
self::assertSame([77], $this->deleted);
|
|
}
|
|
|
|
public function testACacheDirectoryThatCannotBeCreatedIsAFixedText(): void
|
|
{
|
|
$blocker = $this->cacheRoot . 'blocker';
|
|
file_put_contents($blocker, 'x');
|
|
$problems = [];
|
|
$logged = $this->logged(function () use (&$problems, $blocker): void {
|
|
$problems = $this->probe(null, static fn (int $id): string => $blocker . '/seite/')->run();
|
|
});
|
|
|
|
self::assertSame(['Die Testdatei konnte nicht im Cache-Verzeichnis angelegt werden (Details im PHP-Error-Log).'], $problems);
|
|
self::assertStringContainsString('seite', $logged);
|
|
self::assertSame([77], $this->deleted);
|
|
}
|
|
|
|
public function testTheDefaultPurgeIsWpCachePostEdit(): void
|
|
{
|
|
Functions\expect('wp_cache_post_edit')->once()->with(77)->andReturnUsing(function (int $id): int {
|
|
unlink($this->pageDir() . 'index.html');
|
|
|
|
return $id;
|
|
});
|
|
|
|
$probe = new SuperCacheProbe(
|
|
static fn (): bool => true,
|
|
fn (int $id): string => $this->pageDir(),
|
|
null,
|
|
static fn (): bool => true
|
|
);
|
|
|
|
self::assertSame([], $probe->run());
|
|
}
|
|
|
|
public function testTheDefaultStatusSwitchWritesStatusAndSlugStraightToTheDatabaseWithoutHooks(): void
|
|
{
|
|
$wpdb = Mockery::mock();
|
|
$wpdb->posts = 'wp_posts';
|
|
$wpdb->expects('update')->with('wp_posts', ['post_status' => 'private', 'post_name' => 'lit-mcp-check-77'], ['ID' => 77])->andReturn(1);
|
|
$wpdb->expects('update')->with('wp_posts', ['post_status' => 'draft', 'post_name' => 'lit-mcp-check-77'], ['ID' => 77])->andReturn(1);
|
|
$GLOBALS['wpdb'] = $wpdb;
|
|
Functions\expect('clean_post_cache')->never();
|
|
$dropped = [];
|
|
Functions\when('wp_cache_delete')->alias(static function (int $id, string $group) use (&$dropped): bool {
|
|
$dropped[] = $group . ':' . $id;
|
|
|
|
return true;
|
|
});
|
|
|
|
$probe = new SuperCacheProbe(
|
|
static fn (): bool => true,
|
|
fn (int $id): string => $this->pageDir(),
|
|
fn (int $id) => unlink($this->pageDir() . 'index.html')
|
|
);
|
|
|
|
self::assertSame([], $probe->run());
|
|
self::assertSame(['posts:77', 'posts:77'], $dropped, 'the object cache must not keep the old status');
|
|
}
|
|
|
|
public function testTheDefaultEnabledCheckReadsTheWpscGlobal(): void
|
|
{
|
|
$GLOBALS['cache_enabled'] = false;
|
|
self::assertNull((new SuperCacheProbe())->run());
|
|
|
|
$GLOBALS['cache_enabled'] = null;
|
|
self::assertNull((new SuperCacheProbe())->run());
|
|
}
|
|
|
|
private function wpscDirs(string $home, string $current): void
|
|
{
|
|
Functions\when('get_supercache_dir')->justReturn($this->cacheRoot . 'supercache/' . $home . '/');
|
|
Functions\when('get_current_url_supercache_dir')->justReturn($this->cacheRoot . 'supercache/' . $current . '/');
|
|
}
|
|
|
|
public function testTheDefaultCachePathIsTheProbePageDirectoryBelowTheHomeHostDirectory(): void
|
|
{
|
|
$this->wpscDirs('example.test', 'example.test/lit-mcp-check-77');
|
|
|
|
self::assertSame($this->pageDir(), SuperCacheProbe::cacheDirFor(77));
|
|
}
|
|
|
|
/** WPSC derives the host from the request's Host header; the probe must not follow it somewhere else. */
|
|
public function testTheDefaultCachePathIsNullWhenWpscWouldUseAnotherHostThanTheHomeUrl(): void
|
|
{
|
|
$this->wpscDirs('example.test', 'www.example.test/lit-mcp-check-77');
|
|
|
|
self::assertNull(SuperCacheProbe::cacheDirFor(77));
|
|
}
|
|
|
|
public function testTheDefaultCachePathIsNullForAnythingButTheProbePageOfThisSite(): void
|
|
{
|
|
// A plain "?page_id=" address or an empty slug: WPSC resolves it to the home page's directory.
|
|
$this->wpscDirs('example.test', 'example.test');
|
|
self::assertNull(SuperCacheProbe::cacheDirFor(77));
|
|
|
|
$this->wpscDirs('example.test', 'example.test/seite');
|
|
self::assertNull(SuperCacheProbe::cacheDirFor(77));
|
|
|
|
$this->wpscDirs('example.test', 'example.test/lit-mcp-check-77/../../x');
|
|
self::assertNull(SuperCacheProbe::cacheDirFor(77));
|
|
|
|
$this->wpscDirs('example.test', 'example.test/lit-mcp-check-78');
|
|
self::assertNull(SuperCacheProbe::cacheDirFor(77));
|
|
|
|
Functions\when('get_current_url_supercache_dir')->justReturn('/var/www/html/lit-mcp-check-77/');
|
|
self::assertNull(SuperCacheProbe::cacheDirFor(77));
|
|
}
|
|
|
|
public function testTheDefaultCachePathIsNullWhenWpscKnowsNoDirectory(): void
|
|
{
|
|
Functions\when('get_supercache_dir')->justReturn(false);
|
|
Functions\when('get_current_url_supercache_dir')->justReturn(false);
|
|
|
|
self::assertNull(SuperCacheProbe::cacheDirFor(77));
|
|
}
|
|
|
|
public function testOnlyTheKnownCacheFilesOfTheProbePageAreRemoved(): void
|
|
{
|
|
$problems = $this->probe(function (int $id): void {
|
|
// What WPSC leaves after a rebuild-style purge, plus a file that is none of its names.
|
|
rename($this->pageDir() . 'index.html', $this->pageDir() . 'index.html.needs-rebuild');
|
|
touch($this->pageDir() . 'index-https.html');
|
|
touch($this->pageDir() . 'index-https.html.needs-rebuild');
|
|
touch($this->pageDir() . 'fremd.txt');
|
|
})->run();
|
|
|
|
self::assertSame([], $problems);
|
|
self::assertSame(['fremd.txt'], array_values(array_diff(scandir($this->pageDir()), ['.', '..'])));
|
|
}
|
|
|
|
public function testTheCacheDirectoryOfThePageIsRemovedWhenEmpty(): void
|
|
{
|
|
$this->purgingProbe()->run();
|
|
|
|
self::assertDirectoryDoesNotExist($this->pageDir());
|
|
self::assertDirectoryExists($this->cacheRoot . 'supercache/example.test/');
|
|
}
|
|
|
|
// --- P1: the purge must not empty the live cache ------------------------------------------------------------------
|
|
|
|
private function defaultPurgeProbe(): SuperCacheProbe
|
|
{
|
|
return new SuperCacheProbe(
|
|
static fn (): bool => true,
|
|
fn (int $id): string => $this->pageDir(),
|
|
null,
|
|
static fn (): bool => true
|
|
);
|
|
}
|
|
|
|
public function testTheDefaultPurgeRunsWithClearOnEditOffAndTheRelatedPagesFilterOffAndRestoresBoth(): void
|
|
{
|
|
$GLOBALS['wp_cache_clear_on_post_edit'] = 1;
|
|
$events = [];
|
|
$added = null;
|
|
Functions\expect('add_filter')->once()->with('wpsc_delete_related_pages_on_edit', Mockery::type(Closure::class), PHP_INT_MAX)
|
|
->andReturnUsing(static function (string $hook, Closure $callback) use (&$events, &$added): bool {
|
|
$events[] = 'add_filter returning ' . var_export($callback(true), true);
|
|
$added = $callback;
|
|
|
|
return true;
|
|
});
|
|
Functions\expect('remove_filter')->once()->with('wpsc_delete_related_pages_on_edit', Mockery::type(Closure::class), PHP_INT_MAX)
|
|
->andReturnUsing(static function (string $hook, Closure $callback) use (&$events, &$added): bool {
|
|
$events[] = $callback === $added ? 'remove_filter of the same callback' : 'remove_filter of ANOTHER callback';
|
|
|
|
return true;
|
|
});
|
|
Functions\expect('wp_cache_post_edit')->once()->with(77)->andReturnUsing(function (int $id) use (&$events): int {
|
|
$events[] = 'purge with clear_on_post_edit=' . var_export($GLOBALS['wp_cache_clear_on_post_edit'], true);
|
|
unlink($this->pageDir() . 'index.html');
|
|
|
|
return $id;
|
|
});
|
|
|
|
self::assertSame([], $this->defaultPurgeProbe()->run());
|
|
|
|
self::assertSame(
|
|
['add_filter returning false', 'purge with clear_on_post_edit=0', 'remove_filter of the same callback'],
|
|
$events
|
|
);
|
|
self::assertSame(1, $GLOBALS['wp_cache_clear_on_post_edit']);
|
|
}
|
|
|
|
public function testTheDefaultPurgeLeavesAnUnsetClearOnEditGlobalUnset(): void
|
|
{
|
|
unset($GLOBALS['wp_cache_clear_on_post_edit']);
|
|
Functions\when('add_filter')->justReturn(true);
|
|
Functions\when('remove_filter')->justReturn(true);
|
|
Functions\expect('wp_cache_post_edit')->once()->andReturnUsing(function (int $id): int {
|
|
self::assertSame(0, $GLOBALS['wp_cache_clear_on_post_edit']);
|
|
unlink($this->pageDir() . 'index.html');
|
|
|
|
return $id;
|
|
});
|
|
|
|
self::assertSame([], $this->defaultPurgeProbe()->run());
|
|
self::assertArrayNotHasKey('wp_cache_clear_on_post_edit', $GLOBALS);
|
|
}
|
|
|
|
public function testTheDefaultPurgeRestoresGlobalAndFilterWhenWpscThrows(): void
|
|
{
|
|
$GLOBALS['wp_cache_clear_on_post_edit'] = '1';
|
|
Functions\expect('add_filter')->once()->andReturn(true);
|
|
Functions\expect('remove_filter')->once()->with('wpsc_delete_related_pages_on_edit', Mockery::type(Closure::class), PHP_INT_MAX)->andReturn(true);
|
|
Functions\expect('wp_cache_post_edit')->once()->andThrow(new RuntimeException('WPSC kaputt'));
|
|
|
|
$problems = [];
|
|
$this->logged(function () use (&$problems): void {
|
|
$problems = $this->defaultPurgeProbe()->run();
|
|
});
|
|
|
|
self::assertSame(['Super-Cache-Probe abgebrochen (Details im PHP-Error-Log).'], $problems);
|
|
self::assertSame('1', $GLOBALS['wp_cache_clear_on_post_edit']);
|
|
}
|
|
|
|
// --- P2: a status the database refused is no purge result ---------------------------------------------------------
|
|
|
|
public function testARefusedPrivateStatusIsItsOwnFixedProblemAndTheProbeNeverPurges(): void
|
|
{
|
|
$this->refusedStatus = 'private';
|
|
$purged = false;
|
|
$problems = [];
|
|
$logged = $this->logged(function () use (&$problems, &$purged): void {
|
|
$problems = $this->probe(static function (int $id) use (&$purged): void {
|
|
$purged = true;
|
|
})->run();
|
|
});
|
|
|
|
self::assertSame(['Der Status der Testseite konnte nicht gesetzt werden (Details im PHP-Error-Log).'], $problems);
|
|
self::assertStringContainsString('77', $logged);
|
|
self::assertStringContainsString('private', $logged);
|
|
self::assertFalse($purged);
|
|
self::assertSame([77], $this->deleted);
|
|
self::assertSame(['77:private', '77:draft'], $this->statuses);
|
|
}
|
|
|
|
/** @return array<string, array{0: int|false}> */
|
|
public static function refusedUpdates(): array
|
|
{
|
|
return ['database error' => [false], 'no row changed' => [0]];
|
|
}
|
|
|
|
/** @dataProvider refusedUpdates */
|
|
public function testTheDefaultStatusSwitchTreatsAFailedOrEmptyUpdateAsRefused(int|false $result): void
|
|
{
|
|
$wpdb = Mockery::mock();
|
|
$wpdb->posts = 'wp_posts';
|
|
$wpdb->shouldReceive('update')->andReturn($result);
|
|
$GLOBALS['wpdb'] = $wpdb;
|
|
Functions\when('wp_cache_delete')->justReturn(true);
|
|
Functions\expect('wp_cache_post_edit')->never();
|
|
|
|
$problems = [];
|
|
$this->logged(function () use (&$problems): void {
|
|
$problems = (new SuperCacheProbe(
|
|
static fn (): bool => true,
|
|
fn (int $id): string => $this->pageDir(),
|
|
fn (int $id) => null
|
|
))->run();
|
|
});
|
|
|
|
self::assertSame(['Der Status der Testseite konnte nicht gesetzt werden (Details im PHP-Error-Log).'], $problems);
|
|
self::assertSame([77], $this->deleted);
|
|
}
|
|
|
|
public function testAFailingResetToDraftNeitherHidesTheResultNorEndsTheCheck(): void
|
|
{
|
|
$this->throwingStatus = 'draft';
|
|
$problems = [];
|
|
$logged = $this->logged(function () use (&$problems): void {
|
|
$problems = $this->purgingProbe()->run();
|
|
});
|
|
|
|
self::assertSame([], $problems);
|
|
self::assertStringContainsString('Status kaputt', $logged);
|
|
self::assertSame([77], $this->deleted);
|
|
self::assertDirectoryDoesNotExist($this->pageDir());
|
|
}
|
|
|
|
public function testAFailingResetToDraftDoesNotReplaceAnExceptionOfThePurge(): void
|
|
{
|
|
$this->throwingStatus = 'draft';
|
|
$problems = [];
|
|
$logged = $this->logged(function () use (&$problems): void {
|
|
$problems = $this->probe(static function (int $id): never {
|
|
throw new RuntimeException('WPSC kaputt');
|
|
})->run();
|
|
});
|
|
|
|
self::assertSame(['Super-Cache-Probe abgebrochen (Details im PHP-Error-Log).'], $problems);
|
|
self::assertStringContainsString('WPSC kaputt', $logged);
|
|
self::assertSame([77], $this->deleted);
|
|
}
|
|
|
|
public function testARefusedResetToDraftIsLoggedOnly(): void
|
|
{
|
|
$this->refusedStatus = 'draft';
|
|
$problems = [];
|
|
$logged = $this->logged(function () use (&$problems): void {
|
|
$problems = $this->purgingProbe()->run();
|
|
});
|
|
|
|
self::assertSame([], $problems);
|
|
self::assertStringContainsString('draft', $logged);
|
|
}
|
|
}
|