@@ -7,6 +7,7 @@ const { equal, strictEqual, notEqual, fail } = require('node:assert')
77const { setTimeout : sleep } = require ( 'node:timers/promises' )
88const FakeTimers = require ( '@sinonjs/fake-timers' )
99const { Client, interceptors, cacheStores : { MemoryCacheStore } } = require ( '../../index' )
10+ const { makeCacheKey } = require ( '../../lib/util/cache.js' )
1011
1112describe ( 'Cache Interceptor' , ( ) => {
1213 test ( 'caches request' , async ( ) => {
@@ -1984,4 +1985,110 @@ describe('Cache Interceptor', () => {
19841985 }
19851986 } )
19861987 } )
1988+
1989+ describe ( 'determineDeleteAt' , ( ) => {
1990+ test ( 'max-age response has deleteAt proportional to freshness lifetime, not 1 year' , async ( ) => {
1991+ const clock = FakeTimers . install ( { now : 1000 } )
1992+ after ( ( ) => clock . uninstall ( ) )
1993+
1994+ const store = new MemoryCacheStore ( )
1995+ const server = createServer ( { joinDuplicateHeaders : true } , ( _ , res ) => {
1996+ res . setHeader ( 'cache-control' , 'public, max-age=60' )
1997+ res . setHeader ( 'date' , new Date ( clock . now ) . toUTCString ( ) )
1998+ res . sendDate = false
1999+ res . end ( 'short-lived' )
2000+ } ) . listen ( 0 )
2001+
2002+ after ( async ( ) => {
2003+ server . close ( )
2004+ await client . close ( )
2005+ } )
2006+
2007+ await once ( server , 'listening' )
2008+
2009+ const origin = `http://localhost:${ server . address ( ) . port } `
2010+ const client = new Client ( origin )
2011+ . compose ( interceptors . cache ( { store } ) )
2012+
2013+ const res = await client . request ( { origin, method : 'GET' , path : '/delete-at-maxage' } )
2014+ strictEqual ( await res . body . text ( ) , 'short-lived' )
2015+
2016+ const cached = store . get ( makeCacheKey ( { origin, method : 'GET' , path : '/delete-at-maxage' , headers : { } } ) )
2017+
2018+ notEqual ( cached , undefined )
2019+ // deleteAt should be approximately 2x max-age (staleAt + freshnessLifetime),
2020+ // not 1 year out
2021+ const maxExpected = clock . now + ( 60 * 1000 * 3 ) // generous upper bound
2022+ equal ( cached . deleteAt < maxExpected , true , `deleteAt (${ cached . deleteAt } ) should be well under 3x max-age (${ maxExpected } )` )
2023+ equal ( cached . deleteAt > cached . staleAt , true , 'deleteAt should be greater than staleAt to allow revalidation' )
2024+ } )
2025+
2026+ test ( 'immutable response has deleteAt of ~1 year' , async ( ) => {
2027+ const clock = FakeTimers . install ( { now : 1000 } )
2028+ after ( ( ) => clock . uninstall ( ) )
2029+
2030+ const store = new MemoryCacheStore ( )
2031+ const server = createServer ( { joinDuplicateHeaders : true } , ( _ , res ) => {
2032+ res . setHeader ( 'cache-control' , 'public, immutable' )
2033+ res . setHeader ( 'date' , new Date ( clock . now ) . toUTCString ( ) )
2034+ res . sendDate = false
2035+ res . end ( 'immutable-content' )
2036+ } ) . listen ( 0 )
2037+
2038+ after ( async ( ) => {
2039+ server . close ( )
2040+ await client . close ( )
2041+ } )
2042+
2043+ await once ( server , 'listening' )
2044+
2045+ const origin = `http://localhost:${ server . address ( ) . port } `
2046+ const client = new Client ( origin )
2047+ . compose ( interceptors . cache ( { store } ) )
2048+
2049+ const res = await client . request ( { origin, method : 'GET' , path : '/delete-at-immutable' } )
2050+ strictEqual ( await res . body . text ( ) , 'immutable-content' )
2051+
2052+ const cached = store . get ( makeCacheKey ( { origin, method : 'GET' , path : '/delete-at-immutable' , headers : { } } ) )
2053+
2054+ notEqual ( cached , undefined )
2055+ const oneYear = 31536000000
2056+ // deleteAt should be approximately 1 year out
2057+ equal ( cached . deleteAt >= clock . now + oneYear - 1000 , true , `deleteAt (${ cached . deleteAt } ) should be ~1 year out` )
2058+ } )
2059+
2060+ test ( 'stale-while-revalidate extends deleteAt beyond staleAt' , async ( ) => {
2061+ const clock = FakeTimers . install ( { now : 1000 } )
2062+ after ( ( ) => clock . uninstall ( ) )
2063+
2064+ const store = new MemoryCacheStore ( )
2065+ const server = createServer ( { joinDuplicateHeaders : true } , ( _ , res ) => {
2066+ res . setHeader ( 'cache-control' , 'public, max-age=60, stale-while-revalidate=300' )
2067+ res . setHeader ( 'date' , new Date ( clock . now ) . toUTCString ( ) )
2068+ res . sendDate = false
2069+ res . end ( 'swr-content' )
2070+ } ) . listen ( 0 )
2071+
2072+ after ( async ( ) => {
2073+ server . close ( )
2074+ await client . close ( )
2075+ } )
2076+
2077+ await once ( server , 'listening' )
2078+
2079+ const origin = `http://localhost:${ server . address ( ) . port } `
2080+ const client = new Client ( origin )
2081+ . compose ( interceptors . cache ( { store } ) )
2082+
2083+ const res = await client . request ( { origin, method : 'GET' , path : '/delete-at-swr' } )
2084+ strictEqual ( await res . body . text ( ) , 'swr-content' )
2085+
2086+ const cached = store . get ( makeCacheKey ( { origin, method : 'GET' , path : '/delete-at-swr' , headers : { } } ) )
2087+
2088+ notEqual ( cached , undefined )
2089+ // deleteAt should be staleAt + stale-while-revalidate (300s)
2090+ const expectedDeleteAt = cached . staleAt + ( 300 * 1000 )
2091+ equal ( cached . deleteAt , expectedDeleteAt , `deleteAt (${ cached . deleteAt } ) should be staleAt + 300s (${ expectedDeleteAt } )` )
2092+ } )
2093+ } )
19872094} )
0 commit comments