@@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
55import { type FixtureTestContext } from '../../tests/utils/contexts.js'
66import { generateRandomObjectID , startMockBlobStore } from '../../tests/utils/helpers.js'
77
8- import { createRequestContext } from './handlers/request-context.cjs'
8+ import { createRequestContext , type RequestContext } from './handlers/request-context.cjs'
99import { setCacheControlHeaders , setVaryHeaders } from './headers.js'
1010
1111beforeEach < FixtureTestContext > ( async ( ctx ) => {
@@ -194,25 +194,242 @@ describe('headers', () => {
194194 describe ( 'setCacheControlHeaders' , ( ) => {
195195 const defaultUrl = 'https://example.com'
196196
197+ describe ( 'Durable Cache feature flag disabled' , ( ) => {
198+ test ( 'should set permanent, non-durable "netlify-cdn-cache-control" if "cache-control" is not set and "requestContext.usedFsRead" is truthy' , ( ) => {
199+ const headers = new Headers ( )
200+ const request = new Request ( defaultUrl )
201+ vi . spyOn ( headers , 'set' )
202+
203+ const requestContext = createRequestContext ( )
204+ requestContext . usedFsRead = true
205+
206+ setCacheControlHeaders ( headers , request , requestContext , false )
207+
208+ expect ( headers . set ) . toHaveBeenNthCalledWith (
209+ 1 ,
210+ 'cache-control' ,
211+ 'public, max-age=0, must-revalidate' ,
212+ )
213+ expect ( headers . set ) . toHaveBeenNthCalledWith (
214+ 2 ,
215+ 'netlify-cdn-cache-control' ,
216+ 'max-age=31536000' ,
217+ )
218+ } )
219+
220+ describe ( 'route handler responses with a specified `revalidate` value' , ( ) => {
221+ test ( 'should set non-durable SWC=1yr with 1yr TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is `false` (GET)' , ( ) => {
222+ const headers = new Headers ( )
223+ const request = new Request ( defaultUrl )
224+ vi . spyOn ( headers , 'set' )
225+
226+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : false }
227+ setCacheControlHeaders ( headers , request , ctx , false )
228+
229+ expect ( headers . set ) . toHaveBeenCalledTimes ( 1 )
230+ expect ( headers . set ) . toHaveBeenNthCalledWith (
231+ 1 ,
232+ 'netlify-cdn-cache-control' ,
233+ 's-maxage=31536000, stale-while-revalidate=31536000' ,
234+ )
235+ } )
236+
237+ test ( 'should set non-durable SWC=1yr with 1yr TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is `false` (HEAD)' , ( ) => {
238+ const headers = new Headers ( )
239+ const request = new Request ( defaultUrl , { method : 'HEAD' } )
240+ vi . spyOn ( headers , 'set' )
241+
242+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : false }
243+ setCacheControlHeaders ( headers , request , ctx , false )
244+
245+ expect ( headers . set ) . toHaveBeenCalledTimes ( 1 )
246+ expect ( headers . set ) . toHaveBeenNthCalledWith (
247+ 1 ,
248+ 'netlify-cdn-cache-control' ,
249+ 's-maxage=31536000, stale-while-revalidate=31536000' ,
250+ )
251+ } )
252+
253+ test ( 'should set non-durable SWC=1yr with given TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is a number (GET)' , ( ) => {
254+ const headers = new Headers ( )
255+ const request = new Request ( defaultUrl )
256+ vi . spyOn ( headers , 'set' )
257+
258+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : 7200 }
259+ setCacheControlHeaders ( headers , request , ctx , false )
260+
261+ expect ( headers . set ) . toHaveBeenCalledTimes ( 1 )
262+ expect ( headers . set ) . toHaveBeenNthCalledWith (
263+ 1 ,
264+ 'netlify-cdn-cache-control' ,
265+ 's-maxage=7200, stale-while-revalidate=31536000' ,
266+ )
267+ } )
268+
269+ test ( 'should set non-durable SWC=1yr with 1yr TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is a number (HEAD)' , ( ) => {
270+ const headers = new Headers ( )
271+ const request = new Request ( defaultUrl , { method : 'HEAD' } )
272+ vi . spyOn ( headers , 'set' )
273+
274+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : 7200 }
275+ setCacheControlHeaders ( headers , request , ctx , false )
276+
277+ expect ( headers . set ) . toHaveBeenCalledTimes ( 1 )
278+ expect ( headers . set ) . toHaveBeenNthCalledWith (
279+ 1 ,
280+ 'netlify-cdn-cache-control' ,
281+ 's-maxage=7200, stale-while-revalidate=31536000' ,
282+ )
283+ } )
284+ } )
285+ } )
286+
287+ describe ( 'route handler responses with a specified `revalidate` value' , ( ) => {
288+ test ( 'should not set any headers if "cdn-cache-control" is present' , ( ) => {
289+ const givenHeaders = {
290+ 'cdn-cache-control' : 'public, max-age=0, must-revalidate' ,
291+ }
292+ const headers = new Headers ( givenHeaders )
293+ const request = new Request ( defaultUrl )
294+ vi . spyOn ( headers , 'set' )
295+
296+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : false }
297+ setCacheControlHeaders ( headers , request , ctx , true )
298+
299+ expect ( headers . set ) . toHaveBeenCalledTimes ( 0 )
300+ } )
301+
302+ test ( 'should not set any headers if "netlify-cdn-cache-control" is present' , ( ) => {
303+ const givenHeaders = {
304+ 'netlify-cdn-cache-control' : 'public, max-age=0, must-revalidate' ,
305+ }
306+ const headers = new Headers ( givenHeaders )
307+ const request = new Request ( defaultUrl )
308+ vi . spyOn ( headers , 'set' )
309+
310+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : false }
311+ setCacheControlHeaders ( headers , request , ctx , true )
312+
313+ expect ( headers . set ) . toHaveBeenCalledTimes ( 0 )
314+ } )
315+
316+ test ( 'should mark content as stale if "{netlify-,}cdn-cache-control" is not present and "x-nextjs-cache" is "STALE" (GET)' , ( ) => {
317+ const givenHeaders = {
318+ 'x-nextjs-cache' : 'STALE' ,
319+ }
320+ const headers = new Headers ( givenHeaders )
321+ const request = new Request ( defaultUrl )
322+ vi . spyOn ( headers , 'set' )
323+
324+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : false }
325+ setCacheControlHeaders ( headers , request , ctx , true )
326+
327+ expect ( headers . set ) . toHaveBeenCalledTimes ( 1 )
328+ expect ( headers . set ) . toHaveBeenNthCalledWith (
329+ 1 ,
330+ 'netlify-cdn-cache-control' ,
331+ 'public, max-age=0, must-revalidate' ,
332+ )
333+ } )
334+
335+ test ( 'should mark content as stale if "{netlify-,}cdn-cache-control" is not present and "x-nextjs-cache" is "STALE" (HEAD)' , ( ) => {
336+ const givenHeaders = {
337+ 'x-nextjs-cache' : 'STALE' ,
338+ }
339+ const headers = new Headers ( givenHeaders )
340+ const request = new Request ( defaultUrl )
341+ vi . spyOn ( headers , 'set' )
342+
343+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : false }
344+ setCacheControlHeaders ( headers , request , ctx , true )
345+
346+ expect ( headers . set ) . toHaveBeenCalledTimes ( 1 )
347+ expect ( headers . set ) . toHaveBeenNthCalledWith (
348+ 1 ,
349+ 'netlify-cdn-cache-control' ,
350+ 'public, max-age=0, must-revalidate' ,
351+ )
352+ } )
353+
354+ test ( 'should set durable SWC=1yr with 1yr TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is `false` (HEAD)' , ( ) => {
355+ const headers = new Headers ( )
356+ const request = new Request ( defaultUrl , { method : 'HEAD' } )
357+ vi . spyOn ( headers , 'set' )
358+
359+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : false }
360+ setCacheControlHeaders ( headers , request , ctx , true )
361+
362+ expect ( headers . set ) . toHaveBeenCalledTimes ( 1 )
363+ expect ( headers . set ) . toHaveBeenNthCalledWith (
364+ 1 ,
365+ 'netlify-cdn-cache-control' ,
366+ 's-maxage=31536000, stale-while-revalidate=31536000, durable' ,
367+ )
368+ } )
369+
370+ test ( 'should set durable SWC=1yr with given TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is a number (GET)' , ( ) => {
371+ const headers = new Headers ( )
372+ const request = new Request ( defaultUrl )
373+ vi . spyOn ( headers , 'set' )
374+
375+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : 7200 }
376+ setCacheControlHeaders ( headers , request , ctx , true )
377+
378+ expect ( headers . set ) . toHaveBeenCalledTimes ( 1 )
379+ expect ( headers . set ) . toHaveBeenNthCalledWith (
380+ 1 ,
381+ 'netlify-cdn-cache-control' ,
382+ 's-maxage=7200, stale-while-revalidate=31536000, durable' ,
383+ )
384+ } )
385+
386+ test ( 'should set durable SWC=1yr with 1yr TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is a number (HEAD)' , ( ) => {
387+ const headers = new Headers ( )
388+ const request = new Request ( defaultUrl , { method : 'HEAD' } )
389+ vi . spyOn ( headers , 'set' )
390+
391+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : 7200 }
392+ setCacheControlHeaders ( headers , request , ctx , true )
393+
394+ expect ( headers . set ) . toHaveBeenCalledTimes ( 1 )
395+ expect ( headers . set ) . toHaveBeenNthCalledWith (
396+ 1 ,
397+ 'netlify-cdn-cache-control' ,
398+ 's-maxage=7200, stale-while-revalidate=31536000, durable' ,
399+ )
400+ } )
401+
402+ test ( 'should not set any headers on POST request' , ( ) => {
403+ const headers = new Headers ( )
404+ const request = new Request ( defaultUrl , { method : 'POST' } )
405+ vi . spyOn ( headers , 'set' )
406+
407+ const ctx : RequestContext = { ...createRequestContext ( ) , routeHandlerRevalidate : false }
408+ setCacheControlHeaders ( headers , request , ctx , true )
409+
410+ expect ( headers . set ) . toHaveBeenCalledTimes ( 0 )
411+ } )
412+ } )
413+
197414 test ( 'should not set any headers if "cache-control" is not set and "requestContext.usedFsRead" is not truthy' , ( ) => {
198415 const headers = new Headers ( )
199416 const request = new Request ( defaultUrl )
200417 vi . spyOn ( headers , 'set' )
201418
202- setCacheControlHeaders ( headers , request , createRequestContext ( ) )
419+ setCacheControlHeaders ( headers , request , createRequestContext ( ) , true )
203420
204421 expect ( headers . set ) . toHaveBeenCalledTimes ( 0 )
205422 } )
206423
207- test ( 'should set permanent "netlify-cdn-cache-control" if "cache-control" is not set and "requestContext.usedFsRead" is truthy' , ( ) => {
424+ test ( 'should set permanent, durable "netlify-cdn-cache-control" if "cache-control" is not set and "requestContext.usedFsRead" is truthy' , ( ) => {
208425 const headers = new Headers ( )
209426 const request = new Request ( defaultUrl )
210427 vi . spyOn ( headers , 'set' )
211428
212429 const requestContext = createRequestContext ( )
213430 requestContext . usedFsRead = true
214431
215- setCacheControlHeaders ( headers , request , requestContext )
432+ setCacheControlHeaders ( headers , request , requestContext , true )
216433
217434 expect ( headers . set ) . toHaveBeenNthCalledWith (
218435 1 ,
@@ -222,7 +439,7 @@ describe('headers', () => {
222439 expect ( headers . set ) . toHaveBeenNthCalledWith (
223440 2 ,
224441 'netlify-cdn-cache-control' ,
225- 'max-age=31536000' ,
442+ 'max-age=31536000, durable ' ,
226443 )
227444 } )
228445
@@ -235,7 +452,7 @@ describe('headers', () => {
235452 const request = new Request ( defaultUrl )
236453 vi . spyOn ( headers , 'set' )
237454
238- setCacheControlHeaders ( headers , request , createRequestContext ( ) )
455+ setCacheControlHeaders ( headers , request , createRequestContext ( ) , true )
239456
240457 expect ( headers . set ) . toHaveBeenCalledTimes ( 0 )
241458 } )
@@ -249,7 +466,7 @@ describe('headers', () => {
249466 const request = new Request ( defaultUrl )
250467 vi . spyOn ( headers , 'set' )
251468
252- setCacheControlHeaders ( headers , request , createRequestContext ( ) )
469+ setCacheControlHeaders ( headers , request , createRequestContext ( ) , true )
253470
254471 expect ( headers . set ) . toHaveBeenCalledTimes ( 0 )
255472 } )
@@ -262,7 +479,7 @@ describe('headers', () => {
262479 const request = new Request ( defaultUrl )
263480 vi . spyOn ( headers , 'set' )
264481
265- setCacheControlHeaders ( headers , request , createRequestContext ( ) )
482+ setCacheControlHeaders ( headers , request , createRequestContext ( ) , true )
266483
267484 expect ( headers . set ) . toHaveBeenNthCalledWith (
268485 1 ,
@@ -272,7 +489,7 @@ describe('headers', () => {
272489 expect ( headers . set ) . toHaveBeenNthCalledWith (
273490 2 ,
274491 'netlify-cdn-cache-control' ,
275- 'public, max-age=0, must-revalidate' ,
492+ 'public, max-age=0, must-revalidate, durable ' ,
276493 )
277494 } )
278495
@@ -284,7 +501,7 @@ describe('headers', () => {
284501 const request = new Request ( defaultUrl , { method : 'HEAD' } )
285502 vi . spyOn ( headers , 'set' )
286503
287- setCacheControlHeaders ( headers , request , createRequestContext ( ) )
504+ setCacheControlHeaders ( headers , request , createRequestContext ( ) , true )
288505
289506 expect ( headers . set ) . toHaveBeenNthCalledWith (
290507 1 ,
@@ -294,7 +511,7 @@ describe('headers', () => {
294511 expect ( headers . set ) . toHaveBeenNthCalledWith (
295512 2 ,
296513 'netlify-cdn-cache-control' ,
297- 'public, max-age=0, must-revalidate' ,
514+ 'public, max-age=0, must-revalidate, durable ' ,
298515 )
299516 } )
300517
@@ -306,7 +523,7 @@ describe('headers', () => {
306523 const request = new Request ( defaultUrl , { method : 'POST' } )
307524 vi . spyOn ( headers , 'set' )
308525
309- setCacheControlHeaders ( headers , request , createRequestContext ( ) )
526+ setCacheControlHeaders ( headers , request , createRequestContext ( ) , true )
310527
311528 expect ( headers . set ) . toHaveBeenCalledTimes ( 0 )
312529 } )
@@ -319,13 +536,13 @@ describe('headers', () => {
319536 const request = new Request ( defaultUrl )
320537 vi . spyOn ( headers , 'set' )
321538
322- setCacheControlHeaders ( headers , request , createRequestContext ( ) )
539+ setCacheControlHeaders ( headers , request , createRequestContext ( ) , true )
323540
324541 expect ( headers . set ) . toHaveBeenNthCalledWith ( 1 , 'cache-control' , 'public' )
325542 expect ( headers . set ) . toHaveBeenNthCalledWith (
326543 2 ,
327544 'netlify-cdn-cache-control' ,
328- 'public, s-maxage=604800' ,
545+ 'public, s-maxage=604800, durable ' ,
329546 )
330547 } )
331548
@@ -337,25 +554,25 @@ describe('headers', () => {
337554 const request = new Request ( defaultUrl )
338555 vi . spyOn ( headers , 'set' )
339556
340- setCacheControlHeaders ( headers , request , createRequestContext ( ) )
557+ setCacheControlHeaders ( headers , request , createRequestContext ( ) , true )
341558
342559 expect ( headers . set ) . toHaveBeenNthCalledWith ( 1 , 'cache-control' , 'max-age=604800' )
343560 expect ( headers . set ) . toHaveBeenNthCalledWith (
344561 2 ,
345562 'netlify-cdn-cache-control' ,
346- 'max-age=604800, stale-while-revalidate=86400' ,
563+ 'max-age=604800, stale-while-revalidate=86400, durable ' ,
347564 )
348565 } )
349566
350- test ( 'should set default "cache-control" header if it contains only "s-maxage" and "stale-whie -revalidate"' , ( ) => {
567+ test ( 'should set default "cache-control" header if it contains only "s-maxage" and "stale-while -revalidate"' , ( ) => {
351568 const givenHeaders = {
352569 'cache-control' : 's-maxage=604800, stale-while-revalidate=86400' ,
353570 }
354571 const headers = new Headers ( givenHeaders )
355572 const request = new Request ( defaultUrl )
356573 vi . spyOn ( headers , 'set' )
357574
358- setCacheControlHeaders ( headers , request , createRequestContext ( ) )
575+ setCacheControlHeaders ( headers , request , createRequestContext ( ) , true )
359576
360577 expect ( headers . set ) . toHaveBeenNthCalledWith (
361578 1 ,
@@ -365,7 +582,7 @@ describe('headers', () => {
365582 expect ( headers . set ) . toHaveBeenNthCalledWith (
366583 2 ,
367584 'netlify-cdn-cache-control' ,
368- 's-maxage=604800, stale-while-revalidate=86400' ,
585+ 's-maxage=604800, stale-while-revalidate=86400, durable ' ,
369586 )
370587 } )
371588 } )
0 commit comments