feat(cache): Tool lit/cache-clear zum gezielten Leeren des Caches

lit/cache-clear leert den Seiten-Cache von WP Super Cache für die ganze Website (scope all, Recht manage_options) oder einen Beitrag (scope post, Recht edit_post); Probelauf ohne confirm, Ausführen mit confirm: true. Das CapabilityGate sperrt das Tool, wenn WP Super Cache bricht; eine Sperre zur Laufzeit wird als Fehler gemeldet. Listener und Tool teilen die 30-s-Drosselung mit Nachlauf (SuperCachePurger); der Listener ignoriert die Einträge des Tools. Behoben vor dem Commit: die Verdrahtung des Purgers erfasste die Registry zu früh (immer gesperrt), jetzt per Referenz und mit Test über Plugin::integrations().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-06 19:05:21 +01:00
co-authored by Claude Opus 5.5
parent d3e5d4fac0
commit 1ac347e76f
12 changed files with 883 additions and 86 deletions
+41 -5
View File
@@ -34,11 +34,13 @@ use Mockery;
/** The wiring of the composition root: Plugin::integrations() and the steps Plugin::boot() takes with it. */
final class PluginTest extends TestCase
{
private static function registry(ToolAvailability $tools = new ToolAvailability()): IntegrationRegistry
{
private static function registry(
ToolAvailability $tools = new ToolAvailability(),
?FakeChangeLog $log = null
): IntegrationRegistry {
return Plugin::integrations(
Mockery::mock(ContentSchema::class),
FakeChangeLog::forbidden(),
$log ?? FakeChangeLog::forbidden(),
new Limits(),
$tools
);
@@ -95,8 +97,8 @@ final class PluginTest extends TestCase
self::assertArrayNotHasKey('pods', $registry->grouped());
}
/** WP Super Cache has no tools either: registered for its profile only. */
public function testSuperCacheIsRegisteredWithoutBecomingAToolGroup(): void
/** WP Super Cache is registered with its profile; its tool group appears only while the plugin is loaded. */
public function testSuperCacheIsRegisteredAndNoToolGroupWithoutTheCachePlugin(): void
{
$registry = self::registry();
@@ -104,6 +106,40 @@ final class PluginTest extends TestCase
self::assertArrayNotHasKey('wpsc', $registry->grouped());
}
/**
* The purger of the real wiring asks the registry's Compatibility at call time: with WP Super Cache loaded and
* its manifest met, the whole-cache purge runs (a registry captured by value would still be null: always locked).
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testTheWiredPurgerAsksTheCompatibilityOfTheRegistry(): void
{
define('WPCACHEHOME', '/plugins/wp-super-cache/');
eval('function wp_cache_post_edit($id) { $GLOBALS["wpsc_calls"][] = "post:" . $id; }'
. 'function wp_cache_clear_cache($blog = 0) { $GLOBALS["wpsc_calls"][] = "all:" . $blog; }');
$GLOBALS['wpsc_calls'] = [];
$GLOBALS['cache_path'] = '/cache/';
$GLOBALS['blog_cache_dir'] = '/cache/blogs/';
$GLOBALS['cache_enabled'] = true;
Functions\when('get_file_data')->justReturn(['Version' => '3.1.5']);
Functions\when('get_option')->justReturn(false);
Functions\when('get_transient')->justReturn(false);
Functions\when('set_transient')->justReturn(true);
Functions\when('is_multisite')->justReturn(false);
Functions\when('current_user_can')->justReturn(true);
$integrations = (new ReflectionProperty(IntegrationRegistry::class, 'integrations'))->getValue(self::registry(log: new FakeChangeLog(1)));
$wpsc = array_values(array_filter(
$integrations,
static fn ($integration): bool => $integration->id() === 'wpsc'
))[0];
$result = $wpsc->abilities()[0]->execute(['scope' => 'all', 'confirm' => true]);
self::assertSame('purged', $result['outcome']);
self::assertSame(['all:0'], $GLOBALS['wpsc_calls']);
}
/** lit-site-status keeps its order: Divi, Pods, WP Super Cache, The Events Calendar. */
public function testSiteStatusListsDiviPodsSuperCacheAndTheEventsCalendarInThatOrder(): void
{