From 5c108564887635e255191405b2948c79cc3c9f42 Mon Sep 17 00:00:00 2001 From: Tim Kelty Date: Fri, 28 Aug 2026 17:35:16 -0400 Subject: [PATCH 1/2] Queue static cache purge requests - Run console purges immediately - Fall back to purge headers when queue dispatch fails --- README.md | 2 +- src/StaticCache.php | 26 ++++------ src/queue/PurgeStaticCacheJob.php | 48 ++++++++++++++++++ tests/unit/StaticCacheTest.php | 81 +++++++++++++++++++++++++++---- 4 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 src/queue/PurgeStaticCacheJob.php diff --git a/README.md b/README.md index 847bdb29..ee7bbc11 100644 --- a/README.md +++ b/README.md @@ -225,4 +225,4 @@ Most configuration (to Craft and the extension itself) is handled directly by Cl The `StaticCache::EVENT_BEFORE_PURGE` event fires immediately before each tag purge, including the collected end-of-request batch. Listeners can modify its `tags` or cancel the purge. -When a saved element purge proceeds, its non-null site URL is included in tag-based gateway API requests as the optional `fetchUrls` field. URLs are deduplicated, and the gateway asynchronously fetches them after a successful purge to repopulate the cache. Drafts, revisions, deletions, and canceled purges do not send URLs. +When a saved element purge proceeds, a Craft queue job sends its non-null site URL to the gateway as the optional `fetchUrls` field rather than making the gateway request inline. URLs are deduplicated, and the gateway asynchronously fetches them after a successful purge to repopulate the cache. Drafts, revisions, deletions, and canceled purges do not enqueue URLs. If the job cannot be queued, the purge tags fall back to the `Cache-Purge-Tag` response header. diff --git a/src/StaticCache.php b/src/StaticCache.php index fb648f04..c2d7a5d3 100644 --- a/src/StaticCache.php +++ b/src/StaticCache.php @@ -5,6 +5,7 @@ use Craft; use craft\base\ElementInterface; use craft\cloud\events\PurgeEvent; +use craft\cloud\queue\PurgeStaticCacheJob; use craft\events\ElementEvent; use craft\events\InvalidateElementCachesEvent; use craft\events\RegisterCacheOptionsEvent; @@ -400,27 +401,20 @@ private function sendPurgeTagsRequest( return; } - Module::info('Purging tags', [ - 'tags' => $tags, - 'fetchUrls' => $fetchUrls, - ]); - - $payload = [ + $job = new PurgeStaticCacheJob([ 'tags' => $tags->map(fn(StaticCacheTag $tag) => (string) $tag)->values()->all(), - ]; - - if ($fetchUrls->isNotEmpty()) { - $payload['fetchUrls'] = $fetchUrls + 'fetchUrls' => $fetchUrls ->unique() ->values() - ->all(); - } + ->all(), + ]); try { - Helper::createGatewayApiClient()->request('POST', 'cache/purge', [ - RequestOptions::JSON => $payload, - RequestOptions::TIMEOUT => 40, - ]); + if ($isWebResponse) { + Craft::$app->getQueue()->push($job); + } else { + $job->execute(Craft::$app->getQueue()); + } } catch (\Throwable $e) { if ($isWebResponse) { $this->setCacheTagHeader( diff --git a/src/queue/PurgeStaticCacheJob.php b/src/queue/PurgeStaticCacheJob.php new file mode 100644 index 00000000..45280abc --- /dev/null +++ b/src/queue/PurgeStaticCacheJob.php @@ -0,0 +1,48 @@ + $this->tags, + 'fetchUrls' => $this->fetchUrls, + ]); + + $payload = [ + 'tags' => $this->tags, + ]; + + if ($this->fetchUrls !== []) { + $payload['fetchUrls'] = $this->fetchUrls; + } + + Helper::createGatewayApiClient()->request('POST', 'cache/purge', [ + RequestOptions::JSON => $payload, + RequestOptions::TIMEOUT => 40, + ]); + } +} diff --git a/tests/unit/StaticCacheTest.php b/tests/unit/StaticCacheTest.php index 319fa604..07b3709f 100644 --- a/tests/unit/StaticCacheTest.php +++ b/tests/unit/StaticCacheTest.php @@ -8,6 +8,7 @@ use craft\cloud\fs\AssetsFs; use craft\cloud\HeaderEnum; use craft\cloud\Module; +use craft\cloud\queue\PurgeStaticCacheJob; use craft\cloud\signing\RequestSigner; use craft\cloud\StaticCache; use craft\cloud\StaticCacheTag; @@ -15,6 +16,7 @@ use craft\events\ElementEvent; use craft\events\InvalidateElementCachesEvent; use craft\helpers\StringHelper; +use craft\queue\Queue; use GuzzleHttp\HandlerStack; use GuzzleHttp\Promise\Create; use GuzzleHttp\Psr7\Response; @@ -23,6 +25,7 @@ use Psr\Http\Message\RequestInterface; use ReflectionMethod; use ReflectionProperty; +use Throwable; class StaticCacheTest extends Unit { @@ -34,15 +37,19 @@ class StaticCacheTest extends Unit private ?string $requestMethod = null; private ?string $environmentId = null; private ?Module $previousModule = null; + private mixed $previousQueue = null; + private ?CapturingQueue $queue = null; private ?RequestInterface $gatewayRequest = null; private array $gatewayRequestOptions = []; - private ?\Throwable $gatewayException = null; protected function _before(): void { parent::_before(); $this->previousModule = Module::getInstance(); + $this->previousQueue = Craft::$app->getComponents()['queue']; + $this->queue = new CapturingQueue(); + Craft::$app->set('queue', $this->queue); $module = new Module('cloud'); Module::setInstance($module); @@ -56,10 +63,6 @@ protected function _before(): void $module->set('requestSigner', new class(function(RequestInterface $request, array $options) { $this->gatewayRequest = $request; $this->gatewayRequestOptions = $options; - - if ($this->gatewayException) { - throw $this->gatewayException; - } }) extends RequestSigner { public function __construct(private readonly \Closure $capture) { @@ -84,6 +87,7 @@ protected function _after(): void { Craft::$app->getRequest()->setIsCpRequest(null); Craft::$app->getResponse()->clear(); + Craft::$app->set('queue', $this->previousQueue); Module::getInstance()->getConfig()->environmentId = $this->environmentId; Module::setInstance($this->previousModule); @@ -387,12 +391,12 @@ public function testDraftCacheInvalidationDoesNotPurge(): void $this->assertTrue($this->collectionProperty($staticCache, 'tagsToPurge')->isEmpty()); } - public function testFailedFetchRequestFallsBackToPurgeHeader(): void + public function testFailedQueueDispatchFallsBackToPurgeHeader(): void { $staticCache = new StaticCache(); $element = new FetchableElement(['uri' => 'news']); $element->fetchUrl = 'https://example.com/news'; - $this->gatewayException = new \RuntimeException(); + $this->queue->exception = new \RuntimeException(); $this->saveElement($staticCache, $element); @@ -405,9 +409,10 @@ public function testFailedFetchRequestFallsBackToPurgeHeader(): void '123-environment-id:uri:/news', Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), ); + $this->assertNull($this->gatewayRequest); } - public function testGatewayPurgeAllowsBoundedRetries(): void + public function testQueuedGatewayPurgeAllowsBoundedRetries(): void { $staticCache = new StaticCache(); $element = new FetchableElement(['uri' => 'news']); @@ -415,10 +420,29 @@ public function testGatewayPurgeAllowsBoundedRetries(): void $this->saveElement($staticCache, $element); $this->sendPendingPurgeTags($staticCache); + $this->queue->job->execute($this->queue); $this->assertSame(40, $this->gatewayRequestOptions[RequestOptions::TIMEOUT]); } + public function testConsolePurgeRunsGatewayRequestImmediately(): void + { + $response = Craft::$app->getResponse(); + Craft::$app->set('response', new \yii\console\Response()); + + try { + (new StaticCache())->purgeTags('immediate'); + } finally { + Craft::$app->set('response', $response); + } + + $this->assertNull($this->queue->job); + $this->assertSame( + ['tags' => ['immediate']], + json_decode((string) $this->gatewayRequest?->getBody(), true, flags: JSON_THROW_ON_ERROR), + ); + } + public function testBeforePurgeEventCanCancelExistingHeaderTags(): void { $staticCache = new StaticCache(); @@ -491,6 +515,7 @@ public function testOverlongElementUriPurgeUsesOverflowTag(): void $this->saveElement($staticCache, $element); $this->sendPendingPurgeTags($staticCache); + $this->queue->job->execute($this->queue); $payload = json_decode( (string) $this->gatewayRequest?->getBody(), @@ -513,6 +538,7 @@ public function testCancelledElementPurgeDoesNotSendFetch(): void $this->saveElement($staticCache, $element); $this->sendPendingPurgeTags($staticCache); + $this->assertNull($this->queue->job); $this->assertNull($this->gatewayRequest); } @@ -523,12 +549,18 @@ public function testDeletedElementPurgeDoesNotCollectFetch(): void $element->fetchUrl = 'https://example.com/news'; $this->deleteElement($staticCache, $element); + $this->sendPendingPurgeTags($staticCache); $this->assertTrue($this->collectionProperty($staticCache, 'tagsToPurge')->isNotEmpty()); $this->assertTrue($this->collectionProperty($staticCache, 'fetchUrls')->isEmpty()); + $this->assertNull($this->queue->job); + $this->assertSame( + '123-environment-id:uri:/news', + Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), + ); } - public function testSavedElementPurgeRequestIncludesFetchUrls(): void + public function testSavedElementPurgeQueuesGatewayJob(): void { $staticCache = new StaticCache(); $englishElement = new FetchableElement(['uri' => 'news']); @@ -544,6 +576,16 @@ public function testSavedElementPurgeRequestIncludesFetchUrls(): void $this->saveElement($staticCache, $urlLessElement); $this->sendPendingPurgeTags($staticCache); + $this->assertInstanceOf(PurgeStaticCacheJob::class, $this->queue->job); + $this->assertSame(['123-environment-id:uri:/news'], $this->queue->job->tags); + $this->assertSame([ + 'https://example.com/news', + 'https://example.com/fr/nouvelles', + ], $this->queue->job->fetchUrls); + $this->assertNull($this->gatewayRequest); + + $this->queue->job->execute($this->queue); + $payload = json_decode( (string) $this->gatewayRequest?->getBody(), true, @@ -720,3 +762,24 @@ public function getUrl(): ?string return $this->fetchUrl; } } + +class CapturingQueue extends Queue +{ + public mixed $job = null; + public ?Throwable $exception = null; + + public function init(): void + { + } + + public function push($job): ?string + { + if ($this->exception) { + throw $this->exception; + } + + $this->job = $job; + + return '1'; + } +} From 952388b9e253b2dedda59aecb5e3efdfbcebe4cb Mon Sep 17 00:00:00 2001 From: Tim Kelty Date: Mon, 31 Aug 2026 20:01:31 -0400 Subject: [PATCH 2/2] Route all static cache purges through gateway jobs - Remove Cache-Purge-Tag header fallback - Require Cache-Purge-Prefix for gateway purge requests --- README.md | 2 +- src/HeaderEnum.php | 1 - src/Helper.php | 20 +---- src/StaticCache.php | 50 ++--------- src/queue/PurgeStaticCacheJob.php | 1 - tests/unit/StaticCacheTest.php | 137 ++++-------------------------- 6 files changed, 27 insertions(+), 184 deletions(-) diff --git a/README.md b/README.md index ee7bbc11..4c91a710 100644 --- a/README.md +++ b/README.md @@ -225,4 +225,4 @@ Most configuration (to Craft and the extension itself) is handled directly by Cl The `StaticCache::EVENT_BEFORE_PURGE` event fires immediately before each tag purge, including the collected end-of-request batch. Listeners can modify its `tags` or cancel the purge. -When a saved element purge proceeds, a Craft queue job sends its non-null site URL to the gateway as the optional `fetchUrls` field rather than making the gateway request inline. URLs are deduplicated, and the gateway asynchronously fetches them after a successful purge to repopulate the cache. Drafts, revisions, deletions, and canceled purges do not enqueue URLs. If the job cannot be queued, the purge tags fall back to the `Cache-Purge-Tag` response header. +Web requests dispatch tag purges to the gateway with a Craft queue job. When a saved element purge proceeds, its non-null site URL is included as the optional `fetchUrls` field. URLs are deduplicated, and the gateway asynchronously fetches them after a successful purge to repopulate the cache. Drafts, revisions, deletions, and canceled purges do not enqueue URLs. Non-web purges execute the same job immediately. diff --git a/src/HeaderEnum.php b/src/HeaderEnum.php index 6aef5734..89f4520a 100644 --- a/src/HeaderEnum.php +++ b/src/HeaderEnum.php @@ -5,7 +5,6 @@ enum HeaderEnum: string { case CACHE_TAG = 'Cache-Tag'; - case CACHE_PURGE_TAG = 'Cache-Purge-Tag'; case CACHE_PURGE_PREFIX = 'Cache-Purge-Prefix'; case CACHE_CONTROL = 'Cache-Control'; case CDN_CACHE_CONTROL = 'CDN-Cache-Control'; diff --git a/src/Helper.php b/src/Helper.php index 2f9ab5c8..d3c91d35 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -36,36 +36,24 @@ public static function makeGatewayApiRequest(iterable $headers): ResponseInterfa ->values() ->all(); - $tags = []; $prefixes = []; foreach ($headers as $name => $value) { - if (HeaderEnum::CACHE_PURGE_TAG->matches((string) $name)) { - $tags = $normalizeHeaderValue($value); - - if (!empty($tags)) { - break; - } - - continue; - } - if (HeaderEnum::CACHE_PURGE_PREFIX->matches((string) $name)) { $prefixes = $normalizeHeaderValue($value); + break; } } - if (empty($tags) && empty($prefixes)) { - throw new Exception('Gateway API requests require a supported purge header.'); + if (empty($prefixes)) { + throw new Exception('Gateway API requests require a Cache-Purge-Prefix header.'); } return self::createGatewayApiClient()->request( 'POST', 'cache/purge', [ - RequestOptions::JSON => !empty($tags) - ? ['tags' => $tags] - : ['prefixes' => $prefixes], + RequestOptions::JSON => ['prefixes' => $prefixes], ], ); } diff --git a/src/StaticCache.php b/src/StaticCache.php index c2d7a5d3..ab98a017 100644 --- a/src/StaticCache.php +++ b/src/StaticCache.php @@ -25,7 +25,7 @@ use yii\caching\TagDependency; /** - * Static Cache tags can appear in the `Cache-Tag` and `Cache-Purge-Tag` headers. + * Static Cache tags can appear in the `Cache-Tag` header. * The values are comma-separated and can be in several formats: * * - Added by the gateway: @@ -349,14 +349,6 @@ private function sendPurgeTagsRequest( Collection $tags, ?Collection $fetchUrls = null, ): void { - $response = Craft::$app->getResponse(); - $isWebResponse = $response instanceof \craft\web\Response; - - // Add any existing tags from the response headers - if ($isWebResponse) { - $tags->push(...$this->parseCacheTagsFromHeader(HeaderEnum::CACHE_PURGE_TAG->value)); - } - $tags = $this->normalizeCacheTags(...$tags); if ($tags->isEmpty()) { @@ -368,10 +360,6 @@ private function sendPurgeTagsRequest( ]); $this->trigger(self::EVENT_BEFORE_PURGE, $event); - if ($isWebResponse) { - $response->getHeaders()->remove(HeaderEnum::CACHE_PURGE_TAG->value); - } - $overflowTag = $this->overflowTag(); $tags = $event->isValid ? $this->normalizeCacheTags(...$event->tags) @@ -384,23 +372,6 @@ private function sendPurgeTagsRequest( } $fetchUrls ??= Collection::make(); - $sendApiRequest = !$isWebResponse || $fetchUrls->isNotEmpty(); - - if (!$sendApiRequest) { - $tags = $this->truncateCacheTagsForHeader($tags); - - Module::info('Purging tags', [ - 'tags' => $tags, - ]); - - $this->setCacheTagHeader( - HeaderEnum::CACHE_PURGE_TAG->value, - $tags, - ); - - return; - } - $job = new PurgeStaticCacheJob([ 'tags' => $tags->map(fn(StaticCacheTag $tag) => (string) $tag)->values()->all(), 'fetchUrls' => $fetchUrls @@ -409,21 +380,10 @@ private function sendPurgeTagsRequest( ->all(), ]); - try { - if ($isWebResponse) { - Craft::$app->getQueue()->push($job); - } else { - $job->execute(Craft::$app->getQueue()); - } - } catch (\Throwable $e) { - if ($isWebResponse) { - $this->setCacheTagHeader( - HeaderEnum::CACHE_PURGE_TAG->value, - $this->truncateCacheTagsForHeader($tags), - ); - } - - throw $e; + if (Craft::$app->getResponse() instanceof \craft\web\Response) { + Craft::$app->getQueue()->push($job); + } else { + $job->execute(Craft::$app->getQueue()); } } diff --git a/src/queue/PurgeStaticCacheJob.php b/src/queue/PurgeStaticCacheJob.php index 45280abc..ca565d36 100644 --- a/src/queue/PurgeStaticCacheJob.php +++ b/src/queue/PurgeStaticCacheJob.php @@ -42,7 +42,6 @@ public function execute($queue): void Helper::createGatewayApiClient()->request('POST', 'cache/purge', [ RequestOptions::JSON => $payload, - RequestOptions::TIMEOUT => 40, ]); } } diff --git a/tests/unit/StaticCacheTest.php b/tests/unit/StaticCacheTest.php index 07b3709f..2d413ff7 100644 --- a/tests/unit/StaticCacheTest.php +++ b/tests/unit/StaticCacheTest.php @@ -20,7 +20,6 @@ use GuzzleHttp\HandlerStack; use GuzzleHttp\Promise\Create; use GuzzleHttp\Psr7\Response; -use GuzzleHttp\RequestOptions; use Illuminate\Support\Collection; use Psr\Http\Message\RequestInterface; use ReflectionMethod; @@ -40,7 +39,6 @@ class StaticCacheTest extends Unit private mixed $previousQueue = null; private ?CapturingQueue $queue = null; private ?RequestInterface $gatewayRequest = null; - private array $gatewayRequestOptions = []; protected function _before(): void { @@ -60,9 +58,8 @@ protected function _before(): void $this->environmentId = $module->getConfig()->environmentId; $module->getConfig()->environmentId = '123-environment-id'; $module->getConfig()->signingKey = 'test-signing-key'; - $module->set('requestSigner', new class(function(RequestInterface $request, array $options) { + $module->set('requestSigner', new class(function(RequestInterface $request) { $this->gatewayRequest = $request; - $this->gatewayRequestOptions = $options; }) extends RequestSigner { public function __construct(private readonly \Closure $capture) { @@ -72,7 +69,7 @@ public function __construct(private readonly \Closure $capture) public function createHandlerStack(?HandlerStack $handlerStack = null): HandlerStack { return new HandlerStack(function(RequestInterface $request, array $options) { - ($this->capture)($request, $options); + ($this->capture)($request); return Create::promiseFor(new Response(204)); }); @@ -256,19 +253,6 @@ public function testCacheTagOverflowTruncatesTagsThatExceedTheMaximumCount(): vo $this->assertNotContains('tag-1000', $tags); } - public function testPurgeTagsKeepExistingHeaderTags(): void - { - $staticCache = new StaticCache(); - Craft::$app->getResponse()->getHeaders()->set(HeaderEnum::CACHE_PURGE_TAG->value, 'first,second'); - - $staticCache->purgeTags(); - - $this->assertSame( - 'first,second', - Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), - ); - } - public function testPurgeAllUsesOriginAndCdnTags(): void { $staticCache = new StaticCache(); @@ -292,16 +276,12 @@ public function testAssetCdnPurgeUsesEnvironmentFirstTag(): void $method->setAccessible(true); $this->assertTrue($method->invoke($fs, 'image.jpg')); - $this->assertFalse( - Craft::$app->getResponse()->getHeaders()->has(HeaderEnum::CACHE_PURGE_TAG->value), - ); - $this->sendPendingPurgeTags($staticCache); - $this->assertSame( + $this->assertInstanceOf(PurgeStaticCacheJob::class, $this->queue->job); + $this->assertSame([ '123-environment-id:cdn:123-environment-id/assets/image.jpg', - Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), - ); + ], $this->queue->job->tags); } public function testRasterizedAssetCdnPurgeUsesObjectTag(): void @@ -352,10 +332,8 @@ public function testOverlongAssetCdnPurgeUsesOverflowTag(): void $this->assertTrue($method->invoke($fs, str_repeat('x', 1024))); $this->sendPendingPurgeTags($staticCache); - $this->assertSame( - '123-environment-id:overflow', - Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), - ); + $this->assertInstanceOf(PurgeStaticCacheJob::class, $this->queue->job); + $this->assertSame(['123-environment-id:overflow'], $this->queue->job->tags); } public function testBeforePurgeEventCanCancelPurge(): void @@ -368,9 +346,7 @@ public function testBeforePurgeEventCanCancelPurge(): void $staticCache->purgeTags('first'); - $this->assertFalse( - Craft::$app->getResponse()->getHeaders()->has(HeaderEnum::CACHE_PURGE_TAG->value), - ); + $this->assertNull($this->queue->job); } public function testDraftCacheInvalidationDoesNotPurge(): void @@ -391,7 +367,7 @@ public function testDraftCacheInvalidationDoesNotPurge(): void $this->assertTrue($this->collectionProperty($staticCache, 'tagsToPurge')->isEmpty()); } - public function testFailedQueueDispatchFallsBackToPurgeHeader(): void + public function testFailedQueueDispatchThrows(): void { $staticCache = new StaticCache(); $element = new FetchableElement(['uri' => 'news']); @@ -400,29 +376,8 @@ public function testFailedQueueDispatchFallsBackToPurgeHeader(): void $this->saveElement($staticCache, $element); - try { - $this->sendPendingPurgeTags($staticCache); - } catch (\RuntimeException) { - } - - $this->assertSame( - '123-environment-id:uri:/news', - Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), - ); - $this->assertNull($this->gatewayRequest); - } - - public function testQueuedGatewayPurgeAllowsBoundedRetries(): void - { - $staticCache = new StaticCache(); - $element = new FetchableElement(['uri' => 'news']); - $element->fetchUrl = 'https://example.com/news'; - - $this->saveElement($staticCache, $element); + $this->expectException(\RuntimeException::class); $this->sendPendingPurgeTags($staticCache); - $this->queue->job->execute($this->queue); - - $this->assertSame(40, $this->gatewayRequestOptions[RequestOptions::TIMEOUT]); } public function testConsolePurgeRunsGatewayRequestImmediately(): void @@ -443,40 +398,6 @@ public function testConsolePurgeRunsGatewayRequestImmediately(): void ); } - public function testBeforePurgeEventCanCancelExistingHeaderTags(): void - { - $staticCache = new StaticCache(); - Craft::$app->getResponse()->getHeaders()->set(HeaderEnum::CACHE_PURGE_TAG->value, 'first'); - $staticCache->on(StaticCache::EVENT_BEFORE_PURGE, function(PurgeEvent $event) { - $event->isValid = false; - }); - - $staticCache->purgeTags(); - - $this->assertFalse( - Craft::$app->getResponse()->getHeaders()->has(HeaderEnum::CACHE_PURGE_TAG->value), - ); - } - - public function testBeforePurgeEventExceptionPreservesExistingHeaderTags(): void - { - $staticCache = new StaticCache(); - Craft::$app->getResponse()->getHeaders()->set(HeaderEnum::CACHE_PURGE_TAG->value, 'first'); - $staticCache->on(StaticCache::EVENT_BEFORE_PURGE, function() { - throw new \RuntimeException(); - }); - - try { - $staticCache->purgeTags('second'); - } catch (\RuntimeException) { - } - - $this->assertSame( - 'first', - Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), - ); - } - public function testBeforePurgeEventCanReplaceTags(): void { $staticCache = new StaticCache(); @@ -486,10 +407,8 @@ public function testBeforePurgeEventCanReplaceTags(): void $staticCache->purgeTags('first'); - $this->assertSame( - 'second', - Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), - ); + $this->assertInstanceOf(PurgeStaticCacheJob::class, $this->queue->job); + $this->assertSame(['second'], $this->queue->job->tags); } public function testHomepagePurgeUsesUriTag(): void @@ -553,11 +472,9 @@ public function testDeletedElementPurgeDoesNotCollectFetch(): void $this->assertTrue($this->collectionProperty($staticCache, 'tagsToPurge')->isNotEmpty()); $this->assertTrue($this->collectionProperty($staticCache, 'fetchUrls')->isEmpty()); - $this->assertNull($this->queue->job); - $this->assertSame( - '123-environment-id:uri:/news', - Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), - ); + $this->assertInstanceOf(PurgeStaticCacheJob::class, $this->queue->job); + $this->assertSame(['123-environment-id:uri:/news'], $this->queue->job->tags); + $this->assertSame([], $this->queue->job->fetchUrls); } public function testSavedElementPurgeQueuesGatewayJob(): void @@ -597,9 +514,6 @@ public function testSavedElementPurgeQueuesGatewayJob(): void 'https://example.com/news', 'https://example.com/fr/nouvelles', ], $payload['fetchUrls']); - $this->assertFalse( - Craft::$app->getResponse()->getHeaders()->has(HeaderEnum::CACHE_PURGE_TAG->value), - ); } public function testBeforePurgeEventFiresOnceForCollectedBatch(): void @@ -631,10 +545,8 @@ public function testPurgeTagsDoesNotSendCollectedBatch(): void $staticCache->addPurgeTags(['collected']); $staticCache->purgeTags('immediate'); - $this->assertSame( - 'immediate', - Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value), - ); + $this->assertInstanceOf(PurgeStaticCacheJob::class, $this->queue->job); + $this->assertSame(['immediate'], $this->queue->job->tags); $this->assertSame( ['collected'], $this->collectionProperty($staticCache, 'tagsToPurge') @@ -656,21 +568,6 @@ public function testExistingCacheTagHeaderIsSplit(): void ); } - public function testPurgeTagHeaderUsesOverflowFallbackWhenItIsTooLong(): void - { - $staticCache = new StaticCache(); - $tags = Collection::range(1, 1000) - ->map(fn(int $index) => "tag-$index-" . str_repeat('x', 24)) - ->all(); - - $staticCache->purgeTags(...$tags); - - $cachePurgeTagHeader = Craft::$app->getResponse()->getHeaders()->get(HeaderEnum::CACHE_PURGE_TAG->value); - - $this->assertStringStartsWith('123-environment-id:overflow,', $cachePurgeTagHeader); - $this->assertLessThanOrEqual(16 * 1024, StringHelper::byteLength($cachePurgeTagHeader)); - } - private function isCacheable(StaticCache $staticCache): bool { $method = new ReflectionMethod($staticCache, 'isCacheable');