Skip to content

Commit 4378a8c

Browse files
silasjoistenclaude
andauthored
feat(cdn): Add cache validation support for CDN controller (#101)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 801c209 commit 4378a8c

File tree

2 files changed

+47
-13
lines changed

2 files changed

+47
-13
lines changed

src/Controller/CdnController.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Storyblok\Bundle\Cdn\Storage\CdnStorageInterface;
2121
use Storyblok\Bundle\Cdn\Storage\MetadataNotFoundException;
2222
use Symfony\Component\HttpFoundation\BinaryFileResponse;
23+
use Symfony\Component\HttpFoundation\Request;
2324
use Symfony\Component\HttpFoundation\Response;
2425
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
2526
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -39,7 +40,7 @@ public function __construct(
3940
) {
4041
}
4142

42-
public function __invoke(string $id, string $filename, string $extension): Response
43+
public function __invoke(Request $request, string $id, string $filename, string $extension): Response
4344
{
4445
$fileId = new CdnFileId($id);
4546
$fullFilename = \sprintf('%s.%s', $filename, $extension);
@@ -98,6 +99,8 @@ public function __invoke(string $id, string $filename, string $extension): Respo
9899
}
99100
}
100101

102+
$response->isNotModified($request);
103+
101104
return $response;
102105
}
103106
}

tests/Unit/Controller/CdnControllerTest.php

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\Filesystem\Filesystem;
2828
use Symfony\Component\HttpFoundation\BinaryFileResponse;
2929
use Symfony\Component\HttpFoundation\File\File;
30+
use Symfony\Component\HttpFoundation\Request;
3031
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
3132
use function Safe\file_put_contents;
3233
use function Safe\tempnam;
@@ -69,7 +70,7 @@ public function throwsNotFoundWhenMetadataNotFound(): void
6970
$this->expectException(NotFoundHttpException::class);
7071
$this->expectExceptionMessage('Asset not found');
7172

72-
$controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
73+
$controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
7374
}
7475

7576
#[Test]
@@ -101,7 +102,7 @@ public function returnsFileWhenAlreadyDownloaded(): void
101102

102103
$controller = new CdnController($storage, $downloader, null, null, null);
103104

104-
$response = $controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
105+
$response = $controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
105106

106107
self::assertInstanceOf(BinaryFileResponse::class, $response);
107108
self::assertSame(200, $response->getStatusCode());
@@ -158,7 +159,7 @@ public function downloadsFileWhenNotYetDownloaded(): void
158159

159160
$controller = new CdnController($storage, $downloader, null, null, null);
160161

161-
$response = $controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
162+
$response = $controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
162163

163164
self::assertInstanceOf(BinaryFileResponse::class, $response);
164165
self::assertSame(200, $response->getStatusCode());
@@ -190,7 +191,7 @@ public function throwsExceptionWhenDownloadedMetadataIncomplete(): void
190191
$this->expectException(\RuntimeException::class);
191192
$this->expectExceptionMessage('Downloaded file metadata is incomplete');
192193

193-
$controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
194+
$controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
194195
}
195196

196197
#[Test]
@@ -214,7 +215,7 @@ public function setsContentTypeHeader(): void
214215

215216
$controller = new CdnController($storage, $downloader, null, null, null);
216217

217-
$response = $controller->__invoke('ef7436441c4defbf', 'image', 'webp');
218+
$response = $controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'webp');
218219

219220
self::assertSame('image/webp', $response->headers->get('Content-Type'));
220221
}
@@ -241,7 +242,7 @@ public function setsEtagHeader(): void
241242

242243
$controller = new CdnController($storage, $downloader, null, null, null);
243244

244-
$response = $controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
245+
$response = $controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
245246

246247
self::assertSame('"etag-value-123"', $response->getEtag());
247248
}
@@ -267,7 +268,7 @@ public function setsMaxAgeWhenConfigured(): void
267268

268269
$controller = new CdnController($storage, $downloader, 3600, null, null);
269270

270-
$response = $controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
271+
$response = $controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
271272

272273
self::assertSame(3600, $response->getMaxAge());
273274
}
@@ -293,7 +294,7 @@ public function setsSharedMaxAgeWhenConfigured(): void
293294

294295
$controller = new CdnController($storage, $downloader, null, 7200, null);
295296

296-
$response = $controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
297+
$response = $controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
297298

298299
self::assertStringContainsString('s-maxage=7200', (string) $response->headers->get('Cache-Control'));
299300
}
@@ -319,7 +320,7 @@ public function setsPublicCacheDirective(): void
319320

320321
$controller = new CdnController($storage, $downloader, null, null, true);
321322

322-
$response = $controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
323+
$response = $controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
323324

324325
self::assertStringContainsString('public', (string) $response->headers->get('Cache-Control'));
325326
}
@@ -345,7 +346,7 @@ public function setsPrivateCacheDirective(): void
345346

346347
$controller = new CdnController($storage, $downloader, null, null, false);
347348

348-
$response = $controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
349+
$response = $controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
349350

350351
self::assertStringContainsString('private', (string) $response->headers->get('Cache-Control'));
351352
}
@@ -371,14 +372,44 @@ public function combinesAllCacheDirectives(): void
371372

372373
$controller = new CdnController($storage, $downloader, 3600, 7200, true);
373374

374-
$response = $controller->__invoke('ef7436441c4defbf', 'image', 'jpg');
375+
$response = $controller->__invoke(new Request(), 'ef7436441c4defbf', 'image', 'jpg');
375376

376377
$cacheControl = (string) $response->headers->get('Cache-Control');
377378
self::assertStringContainsString('public', $cacheControl);
378379
self::assertStringContainsString('max-age=3600', $cacheControl);
379380
self::assertStringContainsString('s-maxage=7200', $cacheControl);
380381
}
381382

383+
#[Test]
384+
public function returns304WhenEtagMatches(): void
385+
{
386+
$tempFile = $this->createTempFile('content');
387+
$file = new File($tempFile);
388+
389+
$metadata = new CdnFileMetadata(
390+
originalUrl: 'https://a.storyblok.com/f/12345/image.jpg',
391+
contentType: 'image/jpeg',
392+
etag: '"etag-value-123"',
393+
expiresAt: new DateTimeImmutable('+1 day'),
394+
);
395+
396+
$storage = self::createMock(CdnStorageInterface::class);
397+
$storage->method('getMetadata')->willReturn($metadata);
398+
$storage->method('hasFile')->willReturn(true);
399+
$storage->method('getFile')->willReturn($file);
400+
401+
$downloader = self::createMock(FileDownloaderInterface::class);
402+
403+
$controller = new CdnController($storage, $downloader, null, null, null);
404+
405+
$request = new Request();
406+
$request->headers->set('If-None-Match', '"etag-value-123"');
407+
408+
$response = $controller->__invoke($request, 'ef7436441c4defbf', 'image', 'jpg');
409+
410+
self::assertSame(304, $response->getStatusCode());
411+
}
412+
382413
#[Test]
383414
public function constructsFullFilenameFromParts(): void
384415
{
@@ -406,7 +437,7 @@ public function constructsFullFilenameFromParts(): void
406437

407438
$controller = new CdnController($storage, $downloader, null, null, null);
408439

409-
$controller->__invoke('ef7436441c4defbf', 'my-document', 'pdf');
440+
$controller->__invoke(new Request(), 'ef7436441c4defbf', 'my-document', 'pdf');
410441
}
411442

412443
private function createTempFile(string $content): string

0 commit comments

Comments
 (0)