feat(cache): WP Super Cache über den Kompatibilitätskern anbinden
SuperCacheFeature, SuperCacheCompatibility (LockOnFailure, getestet 3.1.5, Version aus dem Plugin-Header) und SuperCacheIntegration ohne Tools. Der SuperCacheListener sammelt Änderungen über lit_mcp_changed und leert einmal am Request-Ende: einen Beitrag per wp_cache_post_edit, mehrere Beiträge oder seitenweite Änderungen per wp_cache_clear_cache (Multisite: eigener Blog). Komplett-Purges höchstens alle 30 s, mit genau einem Nachlauf per WP-Cron. Freigabe-Einträge leeren nichts. Uninstaller räumt Optionen, Transient und Cron-Hook ab; Reiter und lit-site-status zeigen das Profil automatisch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c383b99e7d
commit
05bf890481
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\SuperCache;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Compatibility\Call;
|
||||
use LIT\MCP\Compatibility\Compatibility;
|
||||
use LIT\MCP\Compatibility\Tier;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviProfile;
|
||||
use LIT\MCP\Integrations\Pods\PodsCompatibility;
|
||||
use LIT\MCP\Integrations\SuperCache\SuperCacheCompatibility;
|
||||
use LIT\MCP\Integrations\SuperCache\SuperCacheFeature;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
|
||||
final class SuperCacheCompatibilityTest extends TestCase
|
||||
{
|
||||
public function testDescribesSuperCacheAsALockOnFailurePlugin(): void
|
||||
{
|
||||
$profile = SuperCacheCompatibility::profile();
|
||||
|
||||
self::assertSame('wpsc', $profile->id);
|
||||
self::assertSame('WP Super Cache', $profile->label);
|
||||
self::assertSame(Tier::LockOnFailure, $profile->tier);
|
||||
self::assertSame(['3.1.5'], $profile->testedVersions);
|
||||
self::assertNull($profile->minVersion);
|
||||
self::assertSame(SuperCacheFeature::cases(), $profile->capabilities);
|
||||
}
|
||||
|
||||
/** Argument counts are those the listener passes; wp_cache_clear_cache($blog_id = 0) gets the blog. */
|
||||
public function testTheManifestHoldsTheTwoPurgeFunctionsWithTheirArgumentCounts(): void
|
||||
{
|
||||
$manifest = SuperCacheCompatibility::profile()->manifests[0];
|
||||
$calls = [];
|
||||
foreach ($manifest->calls as $call) {
|
||||
$calls[$call->symbol] = [$call->args, $call->features()];
|
||||
}
|
||||
|
||||
self::assertSame([
|
||||
'wp_cache_post_edit' => [1, [SuperCacheFeature::PurgePost]],
|
||||
'wp_cache_clear_cache' => [1, [SuperCacheFeature::PurgeAll]],
|
||||
], $calls);
|
||||
self::assertContainsOnlyInstancesOf(Call::class, $manifest->calls);
|
||||
}
|
||||
|
||||
/** Both purge functions read $cache_path; wp_cache_post_edit also $blog_cache_dir. */
|
||||
public function testTheGlobalsTheFunctionsReadAreRequired(): void
|
||||
{
|
||||
$manifest = SuperCacheCompatibility::profile()->manifests[0];
|
||||
|
||||
self::assertSame(['WPCACHEHOME'], array_map(static fn ($c) => $c->name, $manifest->constants));
|
||||
$globals = [];
|
||||
foreach ($manifest->globals as $global) {
|
||||
$globals[$global->name] = $global->features();
|
||||
}
|
||||
self::assertSame(
|
||||
['cache_path' => SuperCacheFeature::cases(), 'blog_cache_dir' => [SuperCacheFeature::PurgePost]],
|
||||
$globals
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
public function testActiveAndVersionFollowTheCacheHomeConstantAndThePluginHeader(): void
|
||||
{
|
||||
$profile = SuperCacheCompatibility::profile();
|
||||
self::assertFalse(($profile->active)());
|
||||
self::assertNull(($profile->version)());
|
||||
|
||||
define('WPCACHEHOME', '/plugins/wp-super-cache/');
|
||||
Functions\expect('get_file_data')
|
||||
->once()
|
||||
->with('/plugins/wp-super-cache/wp-cache.php', ['Version' => 'Version'])
|
||||
->andReturn(['Version' => '3.1.5']);
|
||||
|
||||
self::assertTrue(($profile->active)());
|
||||
self::assertSame('3.1.5', ($profile->version)());
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
public function testAnUnreadableHeaderIsAnUnknownVersion(): void
|
||||
{
|
||||
define('WPCACHEHOME', '/plugins/wp-super-cache/');
|
||||
Functions\when('get_file_data')->justReturn(['Version' => '']);
|
||||
|
||||
self::assertNull((SuperCacheCompatibility::profile()->version)());
|
||||
}
|
||||
|
||||
public function testAllProfilesCoexistWithoutIdCollision(): void
|
||||
{
|
||||
$compatibility = new Compatibility(
|
||||
[DiviProfile::profile(), PodsCompatibility::profile(), SuperCacheCompatibility::profile()]
|
||||
);
|
||||
|
||||
self::assertSame(['divi', 'pods', 'wpsc'], array_map(static fn ($p) => $p->id, $compatibility->profiles()));
|
||||
self::assertSame(SuperCacheFeature::PurgeAll, $compatibility->capability('wpsc-purge-all'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\SuperCache;
|
||||
|
||||
use LIT\MCP\Compatibility\Capability;
|
||||
use LIT\MCP\Integrations\SuperCache\SuperCacheFeature;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
|
||||
final class SuperCacheFeatureTest extends TestCase
|
||||
{
|
||||
public function testEveryFeatureIsAWritingCapabilityWithAPrefixedId(): void
|
||||
{
|
||||
foreach (SuperCacheFeature::cases() as $feature) {
|
||||
self::assertInstanceOf(Capability::class, $feature);
|
||||
self::assertSame($feature->value, $feature->id());
|
||||
self::assertNotSame('', $feature->label());
|
||||
self::assertTrue($feature->isWrite());
|
||||
}
|
||||
self::assertSame(
|
||||
['wpsc-purge-post', 'wpsc-purge-all'],
|
||||
array_map(static fn (SuperCacheFeature $f): string => $f->id(), SuperCacheFeature::cases())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\SuperCache;
|
||||
|
||||
use LIT\MCP\Compatibility\ProvidesCompatibility;
|
||||
use LIT\MCP\Integrations\Integration;
|
||||
use LIT\MCP\Integrations\SuperCache\SuperCacheCompatibility;
|
||||
use LIT\MCP\Integrations\SuperCache\SuperCacheIntegration;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
|
||||
final class SuperCacheIntegrationTest extends TestCase
|
||||
{
|
||||
public function testIsAnIntegrationWithoutAbilitiesThatProvidesItsProfile(): void
|
||||
{
|
||||
$integration = new SuperCacheIntegration();
|
||||
|
||||
self::assertInstanceOf(Integration::class, $integration);
|
||||
self::assertInstanceOf(ProvidesCompatibility::class, $integration);
|
||||
self::assertSame('wpsc', $integration->id());
|
||||
self::assertSame('WP Super Cache', $integration->label());
|
||||
self::assertSame([], $integration->abilities());
|
||||
self::assertEquals([SuperCacheCompatibility::profile()], $integration->compatibilities());
|
||||
}
|
||||
|
||||
public function testIsAvailableOnlyWithSuperCache(): void
|
||||
{
|
||||
self::assertSame(defined('WPCACHEHOME'), (new SuperCacheIntegration())->isAvailable());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\SuperCache;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use Closure;
|
||||
use LIT\MCP\Admin\CompatibilityTab;
|
||||
use LIT\MCP\Compatibility\Compatibility;
|
||||
use LIT\MCP\Integrations\SuperCache\SuperCacheCompatibility;
|
||||
use LIT\MCP\Integrations\SuperCache\SuperCacheFeature;
|
||||
use LIT\MCP\Integrations\SuperCache\SuperCacheListener;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use WP_Post;
|
||||
|
||||
final class SuperCacheListenerTest extends TestCase
|
||||
{
|
||||
/** @var list<string> */
|
||||
private array $purges = [];
|
||||
private string $errorLog = '';
|
||||
private string|false $previousErrorLog = false;
|
||||
/** @var list<array{int, string}> wp_schedule_single_event calls: [timestamp, hook] */
|
||||
private array $scheduled = [];
|
||||
/** @var list<array{string, int, int}> set_transient calls */
|
||||
private array $stamped = [];
|
||||
private int|string|false $lastPurge = false;
|
||||
private int|false $planned = false;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->purges = [];
|
||||
$this->scheduled = [];
|
||||
$this->stamped = [];
|
||||
$this->lastPurge = false;
|
||||
$this->planned = false;
|
||||
Functions\when('is_multisite')->justReturn(false);
|
||||
Functions\when('get_transient')->alias(fn (): int|string|false => $this->lastPurge);
|
||||
Functions\when('set_transient')->alias(function (string $key, int $value, int $ttl): bool {
|
||||
$this->stamped[] = [$key, $value, $ttl];
|
||||
|
||||
return true;
|
||||
});
|
||||
Functions\when('wp_next_scheduled')->alias(fn (): int|false => $this->planned);
|
||||
Functions\when('wp_schedule_single_event')->alias(function (int $at, string $hook): bool {
|
||||
$this->scheduled[] = [$at, $hook];
|
||||
|
||||
return true;
|
||||
});
|
||||
$this->errorLog = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
|
||||
$this->previousErrorLog = ini_set('error_log', $this->errorLog);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
ini_set('error_log', (string) $this->previousErrorLog);
|
||||
unlink($this->errorLog);
|
||||
unset($GLOBALS['cache_enabled']);
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/** @param (Closure(string, string): ?int)|null $postIdFor */
|
||||
private function listener(
|
||||
bool $cacheOn = true,
|
||||
bool $usablePost = true,
|
||||
bool $usableAll = true,
|
||||
?Closure $postIdFor = null
|
||||
): SuperCacheListener {
|
||||
return new SuperCacheListener(
|
||||
static fn (SuperCacheFeature $feature): bool => $feature === SuperCacheFeature::PurgePost
|
||||
? $usablePost
|
||||
: $usableAll,
|
||||
static fn (): bool => $cacheOn,
|
||||
function (int $id): void {
|
||||
$this->purges[] = 'post:' . $id;
|
||||
},
|
||||
function (int $blog): void {
|
||||
$this->purges[] = 'all:' . $blog;
|
||||
},
|
||||
$postIdFor ?? static fn (string $type, string $id): ?int => in_array($type, ['page', 'post'], true)
|
||||
? (int) $id
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
private function logged(): string
|
||||
{
|
||||
return (string) file_get_contents($this->errorLog);
|
||||
}
|
||||
|
||||
/** Collect, do not purge: the purge runs once at the end of the request. */
|
||||
public function testAPublicPostPurgesOnlyThatPostAtTheEndOfTheRequest(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
self::assertSame([], $this->purges);
|
||||
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['post:12'], $this->purges);
|
||||
}
|
||||
|
||||
public function testTheSamePostTwiceIsOnePostPurge(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
$listener->onChange('lit/content-update', 'page', '12', 6);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['post:12'], $this->purges);
|
||||
}
|
||||
|
||||
/** More than one post: one whole-cache purge instead of N single ones (loops over many pages). */
|
||||
public function testMoreThanOnePostIsOneWholeCachePurge(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
$listener->onChange('lit/content-update', 'page', '13', 6);
|
||||
$listener->onChange('lit/content-update', 'post', '14', 7);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['all:0'], $this->purges);
|
||||
}
|
||||
|
||||
public function testThePurgeIsDoneOncePerFlush(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
$listener->flush();
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['post:12'], $this->purges);
|
||||
}
|
||||
|
||||
public function testTheShutdownHookIsRegisteredOnceForManyChanges(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
$listener->onChange('lit/content-update', 'page', '13', 6);
|
||||
|
||||
self::assertSame(10, has_action('shutdown', [$listener, 'flush']));
|
||||
$listener->flush();
|
||||
$listener->onChange('lit/content-update', 'page', '14', 7);
|
||||
$listener->flush();
|
||||
self::assertSame(['all:0', 'post:14'], $this->purges);
|
||||
}
|
||||
|
||||
/** @dataProvider siteWideChanges */
|
||||
public function testEverythingElsePurgesTheWholeCache(string $ability, string $type, string $id): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->onChange($ability, $type, $id, null);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['all:0'], $this->purges);
|
||||
}
|
||||
|
||||
/** @return array<string, array{string, string, string}> */
|
||||
public static function siteWideChanges(): array
|
||||
{
|
||||
return [
|
||||
'option' => ['lit/wp-settings-update', 'option', 'blogname'],
|
||||
'menu' => ['lit/menu-set-items', 'nav_menu', '4'],
|
||||
'menu location' => ['lit/menu-assign-location', 'menu_location', 'primary'],
|
||||
'attachment' => ['lit/media-update', 'attachment', '31'],
|
||||
'library module' => ['lit/library-save', 'et_pb_layout', '77'],
|
||||
'divi design' => ['lit/divi-design-update', 'divi_design', 'colors'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testAPostThatIsGoneIsAWholeCachePurge(): void
|
||||
{
|
||||
$listener = $this->listener(postIdFor: static fn (): ?int => null);
|
||||
$listener->onChange('lit/post-trash', 'post', '99', 1);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['all:0'], $this->purges);
|
||||
}
|
||||
|
||||
/** One whole-cache change among single posts: still one purge, no single ones on top. */
|
||||
public function testAWholeCacheChangeSwallowsThePostPurges(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/content-update', 'page', '12', 1);
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'a', 2);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['all:0'], $this->purges);
|
||||
}
|
||||
|
||||
/** Release and withdraw entries of the check workflow change nothing on the site (shared ability name). */
|
||||
public function testReleaseEntriesOfTheCheckWorkflowPurgeNothing(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->onChange(CompatibilityTab::releaseAbility('divi'), 'divi', '5.0.0', 1);
|
||||
$listener->onChange(CompatibilityTab::releaseAbility('pods'), 'pods', '3.3.9.2', 2);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame([], $this->purges);
|
||||
}
|
||||
|
||||
public function testNothingHappensWhileTheCacheIsOff(): void
|
||||
{
|
||||
$listener = $this->listener(cacheOn: false);
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame([], $this->purges);
|
||||
self::assertSame('', $this->logged());
|
||||
self::assertFalse(has_action('shutdown', [$listener, 'flush']));
|
||||
}
|
||||
|
||||
public function testALockedCapabilityPurgesNothingAndLogsOncePerRequest(): void
|
||||
{
|
||||
$listener = $this->listener(usablePost: false);
|
||||
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
$listener->flush();
|
||||
$listener->onChange('lit/content-update', 'page', '13', 6);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame([], $this->purges);
|
||||
self::assertSame(1, substr_count($this->logged(), 'wpsc-purge-post'));
|
||||
}
|
||||
|
||||
/** The capabilities lock separately: a gone purge-post does not stop the whole-cache purge. */
|
||||
public function testEachPurgeNeedsItsOwnCapability(): void
|
||||
{
|
||||
$listener = $this->listener(usablePost: false);
|
||||
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['all:0'], $this->purges);
|
||||
}
|
||||
|
||||
public function testALockedWholeCachePurgeSchedulesNothing(): void
|
||||
{
|
||||
$this->lastPurge = time() - 5;
|
||||
$listener = $this->listener(usableAll: false);
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame([], $this->purges);
|
||||
self::assertSame([], $this->scheduled);
|
||||
}
|
||||
|
||||
public function testOnAMultisiteTheCurrentBlogIsPurged(): void
|
||||
{
|
||||
Functions\when('is_multisite')->justReturn(true);
|
||||
Functions\when('get_current_blog_id')->justReturn(3);
|
||||
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['all:3'], $this->purges);
|
||||
}
|
||||
|
||||
public function testAWholeCachePurgeStampsTheTimeForThirtySeconds(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||
$listener->flush();
|
||||
|
||||
self::assertCount(1, $this->stamped);
|
||||
self::assertSame('lit_mcp_wpsc_purged_at', $this->stamped[0][0]);
|
||||
self::assertEqualsWithDelta(time(), $this->stamped[0][1], 2);
|
||||
self::assertSame(30, $this->stamped[0][2]);
|
||||
}
|
||||
|
||||
/** Single-post purges are never throttled and leave no stamp. */
|
||||
public function testPostPurgesAreNotThrottled(): void
|
||||
{
|
||||
$this->lastPurge = time() - 1;
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame(['post:12'], $this->purges);
|
||||
self::assertSame([], $this->stamped);
|
||||
self::assertSame([], $this->scheduled);
|
||||
}
|
||||
|
||||
/** Inside the lock time: no purge now, exactly one trailing purge at the end of the lock time. */
|
||||
public function testAWholeCachePurgeInsideTheLockTimeSchedulesOneTrailingPurge(): void
|
||||
{
|
||||
$this->lastPurge = time() - 10;
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame([], $this->purges);
|
||||
self::assertSame([], $this->stamped);
|
||||
self::assertSame([[$this->lastPurge + 30, 'lit_mcp_wpsc_trailing_purge']], $this->scheduled);
|
||||
}
|
||||
|
||||
/** Options-table transients come back as strings. */
|
||||
public function testAStringStampFromTheDatabaseThrottlesToo(): void
|
||||
{
|
||||
$this->lastPurge = (string) (time() - 10);
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame([], $this->purges);
|
||||
self::assertCount(1, $this->scheduled);
|
||||
}
|
||||
|
||||
public function testNoSecondTrailingPurgeIsScheduledWhileOneIsPlanned(): void
|
||||
{
|
||||
$this->lastPurge = time() - 10;
|
||||
$this->planned = time() + 20;
|
||||
$listener = $this->listener();
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame([], $this->scheduled);
|
||||
self::assertSame([], $this->purges);
|
||||
}
|
||||
|
||||
public function testTheTrailingPurgeEmptiesTheWholeCache(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->trailingPurge();
|
||||
|
||||
self::assertSame(['all:0'], $this->purges);
|
||||
self::assertCount(1, $this->stamped);
|
||||
}
|
||||
|
||||
public function testTheTrailingPurgeKeepsTheChecks(): void
|
||||
{
|
||||
$this->listener(cacheOn: false)->trailingPurge();
|
||||
$this->listener(usableAll: false)->trailingPurge();
|
||||
|
||||
self::assertSame([], $this->purges);
|
||||
}
|
||||
|
||||
public function testAThrowingTrailingPurgeIsLogged(): void
|
||||
{
|
||||
$listener = new SuperCacheListener(
|
||||
static fn (): bool => true,
|
||||
static fn (): bool => true,
|
||||
null,
|
||||
static function (): void {
|
||||
throw new \RuntimeException('trailing broke');
|
||||
}
|
||||
);
|
||||
|
||||
$listener->trailingPurge();
|
||||
|
||||
self::assertStringContainsString('trailing broke', $this->logged());
|
||||
}
|
||||
|
||||
public function testAThrowingPurgeIsLoggedAndNotRethrown(): void
|
||||
{
|
||||
$listener = new SuperCacheListener(
|
||||
static fn (): bool => true,
|
||||
static fn (): bool => true,
|
||||
static function (): void {
|
||||
throw new \RuntimeException('disk full');
|
||||
},
|
||||
static fn (int $blog): null => null,
|
||||
static fn (): int => 12
|
||||
);
|
||||
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
$listener->flush();
|
||||
|
||||
self::assertStringContainsString('disk full', $this->logged());
|
||||
}
|
||||
|
||||
public function testRegisterHooksTheChangeAnnouncementAndTheTrailingPurge(): void
|
||||
{
|
||||
$listener = $this->listener();
|
||||
$listener->register();
|
||||
|
||||
self::assertSame(10, has_action('lit_mcp_changed', [$listener, 'onChange']));
|
||||
self::assertSame(10, has_action('lit_mcp_wpsc_trailing_purge', [$listener, 'trailingPurge']));
|
||||
}
|
||||
|
||||
/** Plugin wires create(): without WP Super Cache nothing is usable, and with the cache off nothing is asked. */
|
||||
public function testCreateAsksTheCompatibilityOnlyWhenTheCacheIsOn(): void
|
||||
{
|
||||
$compatibility = new Compatibility([SuperCacheCompatibility::profile()]);
|
||||
$listener = SuperCacheListener::create($compatibility);
|
||||
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||
$listener->flush();
|
||||
self::assertSame('', $this->logged());
|
||||
|
||||
$GLOBALS['cache_enabled'] = true;
|
||||
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||
$listener->flush();
|
||||
self::assertStringContainsString('wpsc-purge-all not usable', $this->logged());
|
||||
}
|
||||
|
||||
public function testTheDefaultCacheSwitchIsTheWpscGlobal(): void
|
||||
{
|
||||
$post = new WP_Post();
|
||||
Functions\when('get_post')->justReturn($post);
|
||||
Functions\when('get_post_type_object')->justReturn((object) ['public' => true]);
|
||||
$listener = new SuperCacheListener(static fn (): bool => true);
|
||||
|
||||
$GLOBALS['cache_enabled'] = false;
|
||||
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame('', $this->logged(), 'unset or false cache_enabled: silent, nothing purged');
|
||||
self::assertFalse(has_action('shutdown', [$listener, 'flush']));
|
||||
}
|
||||
|
||||
/** @dataProvider unpurgeablePosts */
|
||||
public function testTheDefaultPostLookupAcceptsOnlyPublicPostTypesThatExist(
|
||||
string $type,
|
||||
string $id,
|
||||
bool $public,
|
||||
bool $exists
|
||||
): void {
|
||||
$GLOBALS['cache_enabled'] = true;
|
||||
Functions\when('get_post_type_object')->justReturn((object) ['public' => $public]);
|
||||
Functions\when('get_post')->justReturn($exists ? new WP_Post() : null);
|
||||
$purged = [];
|
||||
Functions\when('wp_cache_clear_cache')->alias(static function () use (&$purged): void {
|
||||
$purged[] = 'all';
|
||||
});
|
||||
Functions\when('wp_cache_post_edit')->alias(static function () use (&$purged): void {
|
||||
$purged[] = 'post';
|
||||
});
|
||||
|
||||
$listener = new SuperCacheListener(static fn (): bool => true);
|
||||
$listener->onChange('lit/x', $type, $id, 1);
|
||||
$listener->flush();
|
||||
|
||||
self::assertSame([$public && $exists && $type !== 'attachment' && ctype_digit($id) ? 'post' : 'all'], $purged);
|
||||
}
|
||||
|
||||
/** @return array<string, array{string, string, bool, bool}> */
|
||||
public static function unpurgeablePosts(): array
|
||||
{
|
||||
return [
|
||||
'public and present' => ['page', '12', true, true],
|
||||
'not public' => ['et_pb_layout', '12', false, true],
|
||||
'attachment' => ['attachment', '12', true, true],
|
||||
'gone' => ['page', '12', true, false],
|
||||
'id not numeric' => ['page', 'abc', true, true],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user