@@ -54,6 +54,113 @@ describe('Cache Interceptor', () => {
5454 }
5555 } )
5656
57+ test ( 'shared cache does not store responses with Set-Cookie' , async ( ) => {
58+ let requestsToOrigin = 0
59+ const server = createServer ( { joinDuplicateHeaders : true } , ( req , res ) => {
60+ requestsToOrigin ++
61+ const cookie = req . headers . cookie
62+ res . setHeader ( 'cache-control' , 'public, max-age=300' )
63+ res . setHeader ( 'set-cookie' , `session-for-${ cookie } ` )
64+ res . end ( `response for ${ cookie } ` )
65+ } ) . listen ( 0 )
66+
67+ await once ( server , 'listening' )
68+
69+ const client = new Client ( `http://localhost:${ server . address ( ) . port } ` )
70+ . compose ( interceptors . cache ( ) )
71+
72+ try {
73+ {
74+ const res = await client . request ( {
75+ origin : 'localhost' ,
76+ method : 'GET' ,
77+ path : '/' ,
78+ headers : { cookie : 'user-a' }
79+ } )
80+ equal ( requestsToOrigin , 1 )
81+ equal ( res . headers [ 'set-cookie' ] , 'session-for-user-a' )
82+ strictEqual ( await res . body . text ( ) , 'response for user-a' )
83+ }
84+
85+ {
86+ const res = await client . request ( {
87+ origin : 'localhost' ,
88+ method : 'GET' ,
89+ path : '/' ,
90+ headers : { cookie : 'user-b' }
91+ } )
92+ equal ( requestsToOrigin , 2 )
93+ equal ( res . headers [ 'set-cookie' ] , 'session-for-user-b' )
94+ strictEqual ( await res . body . text ( ) , 'response for user-b' )
95+ }
96+ } finally {
97+ await client . close ( )
98+ await new Promise ( resolve => server . close ( resolve ) )
99+ }
100+ } )
101+
102+ test ( 'shared cache does not serve existing entries with Set-Cookie' , async ( ) => {
103+ let requestsToOrigin = 0
104+ const server = createServer ( { joinDuplicateHeaders : true } , ( req , res ) => {
105+ requestsToOrigin ++
106+ const cookie = req . headers . cookie
107+ res . setHeader ( 'cache-control' , 'public, max-age=300' )
108+ res . setHeader ( 'set-cookie' , `session-for-${ cookie } ` )
109+ res . end ( `response for ${ cookie } ` )
110+ } ) . listen ( 0 )
111+
112+ await once ( server , 'listening' )
113+
114+ const store = new MemoryCacheStore ( )
115+ const origin = `http://localhost:${ server . address ( ) . port } `
116+ const privateClient = new Client ( origin )
117+ . compose ( interceptors . cache ( { store, type : 'private' } ) )
118+ const sharedClient = new Client ( origin )
119+ . compose ( interceptors . cache ( { store, type : 'shared' } ) )
120+
121+ try {
122+ {
123+ const res = await privateClient . request ( {
124+ origin : 'localhost' ,
125+ method : 'GET' ,
126+ path : '/' ,
127+ headers : { cookie : 'user-a' }
128+ } )
129+ equal ( requestsToOrigin , 1 )
130+ equal ( res . headers [ 'set-cookie' ] , 'session-for-user-a' )
131+ strictEqual ( await res . body . text ( ) , 'response for user-a' )
132+ }
133+
134+ {
135+ const res = await privateClient . request ( {
136+ origin : 'localhost' ,
137+ method : 'GET' ,
138+ path : '/' ,
139+ headers : { cookie : 'user-a' }
140+ } )
141+ equal ( requestsToOrigin , 1 )
142+ equal ( res . headers [ 'set-cookie' ] , 'session-for-user-a' )
143+ strictEqual ( await res . body . text ( ) , 'response for user-a' )
144+ }
145+
146+ {
147+ const res = await sharedClient . request ( {
148+ origin : 'localhost' ,
149+ method : 'GET' ,
150+ path : '/' ,
151+ headers : { cookie : 'user-b' }
152+ } )
153+ equal ( requestsToOrigin , 2 )
154+ equal ( res . headers [ 'set-cookie' ] , 'session-for-user-b' )
155+ strictEqual ( await res . body . text ( ) , 'response for user-b' )
156+ }
157+ } finally {
158+ await privateClient . close ( )
159+ await sharedClient . close ( )
160+ await new Promise ( resolve => server . close ( resolve ) )
161+ }
162+ } )
163+
57164 test ( 'vary directives used to decide which response to use' , async ( ) => {
58165 let requestsToOrigin = 0
59166 const server = createServer ( { joinDuplicateHeaders : true } , ( req , res ) => {
@@ -1237,20 +1344,20 @@ describe('Cache Interceptor', () => {
12371344
12381345 test ( 'qualified no-cache/private with OWS around = strip named headers from cached responses' , async ( ) => {
12391346 for ( const cacheControl of [
1240- 'public, max-age=60, no-cache= "set-cookie "' ,
1241- 'public, max-age=60, no-cache ="set-cookie "' ,
1242- 'public, max-age=60, no-cache \t= \t"set-cookie "' ,
1243- 'public, max-age=60, no-cache="set-cookie "\t, immutable' ,
1244- 'public, max-age=60, private= "set-cookie "' ,
1245- 'public, max-age=60, private ="set-cookie "' ,
1246- 'public, max-age=60, private\t=\t"set-cookie "' ,
1247- 'public, max-age=60, private="set-cookie "\t, immutable'
1347+ 'public, max-age=60, no-cache= "x-secret "' ,
1348+ 'public, max-age=60, no-cache ="x-secret "' ,
1349+ 'public, max-age=60, no-cache \t= \t"x-secret "' ,
1350+ 'public, max-age=60, no-cache="x-secret "\t, immutable' ,
1351+ 'public, max-age=60, private= "x-secret "' ,
1352+ 'public, max-age=60, private ="x-secret "' ,
1353+ 'public, max-age=60, private\t=\t"x-secret "' ,
1354+ 'public, max-age=60, private="x-secret "\t, immutable'
12481355 ] ) {
12491356 let requestToOrigin = 0
12501357 const server = createServer ( { joinDuplicateHeaders : true } , ( _ , res ) => {
12511358 requestToOrigin ++
12521359 res . setHeader ( 'cache-control' , cacheControl )
1253- res . setHeader ( 'set-cookie ' , 'session= secret' )
1360+ res . setHeader ( 'x-secret ' , 'secret' )
12541361 res . end ( 'ok' )
12551362 } ) . listen ( 0 )
12561363
@@ -1269,14 +1376,14 @@ describe('Cache Interceptor', () => {
12691376 {
12701377 const res = await client . request ( request )
12711378 equal ( requestToOrigin , 1 , cacheControl )
1272- equal ( res . headers [ 'set-cookie ' ] , 'session= secret' , cacheControl )
1379+ equal ( res . headers [ 'x-secret ' ] , 'secret' , cacheControl )
12731380 strictEqual ( await res . body . text ( ) , 'ok' )
12741381 }
12751382
12761383 {
12771384 const res = await client . request ( request )
12781385 equal ( requestToOrigin , 1 , cacheControl )
1279- equal ( res . headers [ 'set-cookie ' ] , undefined , cacheControl )
1386+ equal ( res . headers [ 'x-secret ' ] , undefined , cacheControl )
12801387 strictEqual ( await res . body . text ( ) , 'ok' )
12811388 }
12821389 } finally {
@@ -1291,8 +1398,8 @@ describe('Cache Interceptor', () => {
12911398 const server = createServer ( { joinDuplicateHeaders : true } , ( _ , res ) => {
12921399 requestToOrigin ++
12931400 res . setHeader ( 'cache-control' , 's-maxage=10' )
1294- res . setHeader ( 'connection' , [ 'Set-Cookie , X-Secret' , 'Keep-Alive, X-Empty' ] )
1295- res . setHeader ( 'set-cookie ' , 'session= secret' )
1401+ res . setHeader ( 'connection' , [ 'X-Other-Secret , X-Secret' , 'Keep-Alive, X-Empty' ] )
1402+ res . setHeader ( 'x-other-secret ' , 'other secret' )
12961403 res . setHeader ( 'x-secret' , 'secret' )
12971404 res . setHeader ( 'x-empty' , '' )
12981405 res . end ( 'ok' )
@@ -1317,7 +1424,7 @@ describe('Cache Interceptor', () => {
13171424 {
13181425 const res = await client . request ( request )
13191426 equal ( requestToOrigin , 1 )
1320- equal ( res . headers [ 'set-cookie ' ] , 'session= secret' )
1427+ equal ( res . headers [ 'x-other-secret ' ] , 'other secret' )
13211428 equal ( res . headers [ 'x-secret' ] , 'secret' )
13221429 equal ( res . headers [ 'x-empty' ] , '' )
13231430 strictEqual ( await res . body . text ( ) , 'ok' )
@@ -1326,7 +1433,7 @@ describe('Cache Interceptor', () => {
13261433 {
13271434 const res = await client . request ( request )
13281435 equal ( requestToOrigin , 1 )
1329- equal ( res . headers [ 'set-cookie ' ] , undefined )
1436+ equal ( res . headers [ 'x-other-secret ' ] , undefined )
13301437 equal ( res . headers [ 'x-secret' ] , undefined )
13311438 equal ( res . headers [ 'x-empty' ] , undefined )
13321439 strictEqual ( await res . body . text ( ) , 'ok' )
@@ -1607,6 +1714,7 @@ describe('Cache Interceptor', () => {
16071714 for ( const testCase of [
16081715 { name : 'no-store' , headers : { 'cache-control' : 'no-store' } } ,
16091716 { name : 'private' , headers : { 'cache-control' : 'private' } } ,
1717+ { name : 'set-cookie' , headers : { 'set-cookie' : 'session=secret' } } ,
16101718 { name : 'vary-star' , headers : { vary : '*' } } ,
16111719 { name : 'malformed-vary' , headers : { vary : 'cookie authorization' } }
16121720 ] ) {
@@ -1840,6 +1948,7 @@ describe('Cache Interceptor', () => {
18401948 for ( const testCase of [
18411949 { name : 'no-store' , headers : { 'cache-control' : 'no-store' } } ,
18421950 { name : 'private' , headers : { 'cache-control' : 'private' } } ,
1951+ { name : 'set-cookie' , headers : { 'set-cookie' : 'session=secret' } } ,
18431952 { name : 'vary-star' , headers : { vary : '*' } } ,
18441953 { name : 'malformed-vary' , headers : { vary : 'cookie authorization' } } ,
18451954 { name : 'explicitly-stale' , headers : { 'cache-control' : 'public, max-age=0' } } ,
@@ -1922,6 +2031,75 @@ describe('Cache Interceptor', () => {
19222031 }
19232032 } )
19242033
2034+ test ( 'stale-while-revalidate evicts a shared entry when its replacement has Set-Cookie' , async ( ) => {
2035+ const clock = FakeTimers . install ( {
2036+ toFake : [ 'Date' ]
2037+ } )
2038+
2039+ let requestsToOrigin = 0
2040+ let conditionalRequests = 0
2041+ const server = createServer ( { joinDuplicateHeaders : true } , ( req , res ) => {
2042+ requestsToOrigin ++
2043+ res . setHeader ( 'date' , new Date ( ) . toUTCString ( ) )
2044+
2045+ if ( req . headers [ 'if-none-match' ] || req . headers [ 'if-modified-since' ] ) {
2046+ conditionalRequests ++
2047+ res . setHeader ( 'cache-control' , 'public, max-age=60' )
2048+ res . setHeader ( 'set-cookie' , 'session=secret' )
2049+ res . end ( 'personalized' )
2050+ return
2051+ }
2052+
2053+ if ( requestsToOrigin === 1 ) {
2054+ res . setHeader ( 'cache-control' , 'public, max-age=1, stale-while-revalidate=10' )
2055+ res . setHeader ( 'etag' , '"cached"' )
2056+ res . end ( 'cached' )
2057+ return
2058+ }
2059+
2060+ res . setHeader ( 'cache-control' , 'public, max-age=60' )
2061+ res . end ( 'refetched' )
2062+ } ) . listen ( 0 )
2063+
2064+ const client = new Client ( `http://localhost:${ server . address ( ) . port } ` )
2065+ . compose ( interceptors . cache ( ) )
2066+
2067+ try {
2068+ await once ( server , 'listening' )
2069+
2070+ const request = {
2071+ origin : 'localhost' ,
2072+ method : 'GET' ,
2073+ path : '/'
2074+ }
2075+
2076+ {
2077+ const res = await client . request ( request )
2078+ strictEqual ( await res . body . text ( ) , 'cached' )
2079+ }
2080+
2081+ clock . tick ( 1500 )
2082+
2083+ {
2084+ const res = await client . request ( request )
2085+ strictEqual ( await res . body . text ( ) , 'cached' )
2086+ }
2087+
2088+ await sleep ( 100 )
2089+ equal ( conditionalRequests , 1 )
2090+
2091+ {
2092+ const res = await client . request ( request )
2093+ equal ( requestsToOrigin , 3 )
2094+ strictEqual ( await res . body . text ( ) , 'refetched' )
2095+ }
2096+ } finally {
2097+ await client . close ( )
2098+ await new Promise ( resolve => server . close ( resolve ) )
2099+ clock . uninstall ( )
2100+ }
2101+ } )
2102+
19252103 test ( 'stale-if-error (response)' , async ( ) => {
19262104 const clock = FakeTimers . install ( {
19272105 toFake : [ 'Date' ]
0 commit comments