Skip to content

Commit cfec10c

Browse files
authored
Restore cache tests & fix max-age behavior (#4198)
* Restore cache tests Signed-off-by: Matteo Collina <[email protected]> * fixed caching Signed-off-by: Matteo Collina <[email protected]> * fixup Signed-off-by: Matteo Collina <[email protected]> * fixup Signed-off-by: Matteo Collina <[email protected]> --------- Signed-off-by: Matteo Collina <[email protected]>
1 parent 0981779 commit cfec10c

File tree

5 files changed

+334
-13
lines changed

5 files changed

+334
-13
lines changed

lib/cache/memory-cache-store.js

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,9 @@ class MemoryCacheStore {
7676
const topLevelKey = `${key.origin}:${key.path}`
7777

7878
const now = Date.now()
79-
const entry = this.#entries.get(topLevelKey)?.find((entry) => (
80-
entry.deleteAt > now &&
81-
entry.method === key.method &&
82-
(entry.vary == null || Object.keys(entry.vary).every(headerName => {
83-
if (entry.vary[headerName] === null) {
84-
return key.headers[headerName] === undefined
85-
}
79+
const entries = this.#entries.get(topLevelKey)
8680

87-
return entry.vary[headerName] === key.headers[headerName]
88-
}))
89-
))
81+
const entry = entries ? findEntry(key, entries, now) : null
9082

9183
return entry == null
9284
? undefined
@@ -140,10 +132,17 @@ class MemoryCacheStore {
140132
entries = []
141133
store.#entries.set(topLevelKey, entries)
142134
}
143-
entries.push(entry)
135+
const previousEntry = findEntry(key, entries, Date.now())
136+
if (previousEntry) {
137+
const index = entries.indexOf(previousEntry)
138+
entries.splice(index, 1, entry)
139+
store.#size -= previousEntry.size
140+
} else {
141+
entries.push(entry)
142+
store.#count += 1
143+
}
144144

145145
store.#size += entry.size
146-
store.#count += 1
147146

148147
if (store.#size > store.#maxSize || store.#count > store.#maxCount) {
149148
for (const [key, entries] of store.#entries) {
@@ -180,4 +179,18 @@ class MemoryCacheStore {
180179
}
181180
}
182181

182+
function findEntry (key, entries, now) {
183+
return entries.find((entry) => (
184+
entry.deleteAt > now &&
185+
entry.method === key.method &&
186+
(entry.vary == null || Object.keys(entry.vary).every(headerName => {
187+
if (entry.vary[headerName] === null) {
188+
return key.headers[headerName] === undefined
189+
}
190+
191+
return entry.vary[headerName] === key.headers[headerName]
192+
}))
193+
))
194+
}
195+
183196
module.exports = MemoryCacheStore

test/cache-interceptor/cache-store-test-utils.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,87 @@ function cacheStoreTests (CacheStore) {
164164
equal(await store.get(key), undefined)
165165
})
166166

167+
test('a stale request is overwritten', async () => {
168+
const clock = FakeTimers.install({
169+
shouldClearNativeTimers: true
170+
})
171+
172+
after(() => clock.uninstall())
173+
174+
/**
175+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
176+
*/
177+
const key = {
178+
origin: 'localhost',
179+
path: '/',
180+
method: 'GET',
181+
headers: {}
182+
}
183+
184+
/**
185+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
186+
*/
187+
const value = {
188+
statusCode: 200,
189+
statusMessage: '',
190+
headers: { foo: 'bar' },
191+
cacheControlDirectives: {},
192+
cachedAt: Date.now(),
193+
staleAt: Date.now() + 1000,
194+
// deleteAt is different because stale-while-revalidate, stale-if-error, ...
195+
deleteAt: Date.now() + 5000
196+
}
197+
198+
const body = [Buffer.from('asd'), Buffer.from('123')]
199+
200+
const store = new CacheStore()
201+
202+
// Sanity check
203+
equal(store.get(key), undefined)
204+
205+
{
206+
const writable = store.createWriteStream(key, value)
207+
notEqual(writable, undefined)
208+
writeBody(writable, body)
209+
}
210+
211+
clock.tick(1500)
212+
213+
{
214+
const result = await store.get(structuredClone(key))
215+
notEqual(result, undefined)
216+
await compareGetResults(result, value, body)
217+
}
218+
219+
/**
220+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
221+
*/
222+
const value2 = {
223+
statusCode: 200,
224+
statusMessage: '',
225+
headers: { foo: 'baz' },
226+
cacheControlDirectives: {},
227+
cachedAt: Date.now(),
228+
staleAt: Date.now() + 1000,
229+
// deleteAt is different because stale-while-revalidate, stale-if-error, ...
230+
deleteAt: Date.now() + 5000
231+
}
232+
233+
const body2 = [Buffer.from('foo'), Buffer.from('123')]
234+
235+
{
236+
const writable = store.createWriteStream(key, value2)
237+
notEqual(writable, undefined)
238+
writeBody(writable, body2)
239+
}
240+
241+
{
242+
const result = await store.get(structuredClone(key))
243+
notEqual(result, undefined)
244+
await compareGetResults(result, value2, body2)
245+
}
246+
})
247+
167248
test('vary directives used to decide which response to use', async () => {
168249
/**
169250
* @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}

test/cache-interceptor/cache-tests-worker.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ if (environment.ignoredTests) {
5050
const client = new Agent().compose(interceptors.cache(environment.opts))
5151
setGlobalDispatcher(client)
5252

53+
globalThis.fetch = fetch
54+
5355
// Run the suite
54-
await runTestSuite(tests, fetch, true, process.env.BASE_URL)
56+
await runTestSuite(tests, true, process.env.BASE_URL)
5557

5658
let exitCode = 0
5759

@@ -207,6 +209,10 @@ function printStats (stats) {
207209
retried
208210
} = stats
209211

212+
if (total < 0) {
213+
throw new Error('Total tests cannot be negative')
214+
}
215+
210216
console.log(`\n Total tests: ${total}`)
211217
console.log(` ${styleText('gray', 'Skipped')}: ${skipped} (${((skipped / total) * 100).toFixed(1)}%)`)
212218
console.log(` ${styleText('green', 'Passed')}: ${passed} (${((passed / total) * 100).toFixed(1)}%)`)

test/cache-interceptor/sqlite-cache-store-tests.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ test('SqliteCacheStore works nicely with multiple stores', async (t) => {
3838
})
3939

4040
t.after(async () => {
41+
storeA.close()
42+
storeB.close()
4143
await rm(sqliteLocation)
4244
})
4345

0 commit comments

Comments
 (0)