- the Nitro middleware answers redirect (301/302, Location from the answer only, same-site targets as paths as in v0.1.1) and gone (410) before the render and the route cache, sets X-Robots-Tag, and answers 404 for a pagination.invalid answer; other not_found answers stay with Nuxt - resolve answers cached in an LRU (seo.resolveCacheTtl, default 60 s, 1000 entries) with in-flight de-duplication; a clear detaches loads in flight so no answer fetched before a purge is stored after it - the playground route-caches /blog/** (swr 300)
106 lines
3.8 KiB
TypeScript
106 lines
3.8 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import { createLru } from '../server/lib/lru.ts'
|
|
|
|
function clock(start = 1000) {
|
|
let t = start
|
|
return { now: () => t, advance: (ms: number) => (t += ms) }
|
|
}
|
|
|
|
test('lru: get answers a value within its TTL and nothing after it', () => {
|
|
const c = clock()
|
|
const lru = createLru<string>({ max: 10, ttlMs: 60_000, now: c.now })
|
|
lru.set('/a', 'A')
|
|
assert.equal(lru.get('/a'), 'A')
|
|
c.advance(59_999)
|
|
assert.equal(lru.get('/a'), 'A')
|
|
c.advance(1)
|
|
assert.equal(lru.get('/a'), undefined)
|
|
assert.equal(lru.size, 0)
|
|
})
|
|
|
|
test('lru: the least recently used entry goes first when full', () => {
|
|
const lru = createLru<number>({ max: 2, ttlMs: 60_000 })
|
|
lru.set('a', 1)
|
|
lru.set('b', 2)
|
|
assert.equal(lru.get('a'), 1) // a is now the most recent
|
|
lru.set('c', 3)
|
|
assert.equal(lru.get('b'), undefined)
|
|
assert.equal(lru.get('a'), 1)
|
|
assert.equal(lru.get('c'), 3)
|
|
assert.equal(lru.size, 2)
|
|
})
|
|
|
|
test('lru: load calls the loader once for concurrent requests of one key', async () => {
|
|
const lru = createLru<string>({ max: 10, ttlMs: 60_000 })
|
|
let calls = 0
|
|
let release: (v: string) => void = () => {}
|
|
const loader = () => {
|
|
calls++
|
|
return new Promise<string>((r) => (release = r))
|
|
}
|
|
const one = lru.load('/x', loader)
|
|
const two = lru.load('/x', loader)
|
|
release('X')
|
|
assert.deepEqual(await Promise.all([one, two]), ['X', 'X'])
|
|
assert.equal(calls, 1)
|
|
assert.equal(await lru.load('/x', loader), 'X')
|
|
assert.equal(calls, 1, 'a cached value needs no call')
|
|
})
|
|
|
|
test('lru: a value the cacheable test refuses is not stored, and a failed load is not cached', async () => {
|
|
const lru = createLru<string | null>({ max: 10, ttlMs: 60_000 })
|
|
let calls = 0
|
|
await lru.load('/n', async () => (calls++, null), (v) => v !== null)
|
|
await lru.load('/n', async () => (calls++, null), (v) => v !== null)
|
|
assert.equal(calls, 2)
|
|
await assert.rejects(lru.load('/e', async () => Promise.reject(new Error('down'))))
|
|
assert.equal(await lru.load('/e', async () => 'ok'), 'ok')
|
|
})
|
|
|
|
test('lru: clear empties the cache and an answer started before it is never stored', async () => {
|
|
const lru = createLru<string>({ max: 10, ttlMs: 60_000 })
|
|
let release: (v: string) => void = () => {}
|
|
const stale = lru.load('/p', () => new Promise<string>((r) => (release = r)))
|
|
lru.clear()
|
|
release('stale')
|
|
assert.equal(await stale, 'stale', 'the request in flight still gets its answer')
|
|
assert.equal(lru.get('/p'), undefined, 'but it is not cached after the purge')
|
|
let calls = 0
|
|
assert.equal(await lru.load('/p', async () => (calls++, 'fresh')), 'fresh')
|
|
assert.equal(calls, 1)
|
|
})
|
|
|
|
test('lru: a load after clear does not join a load started before it', async () => {
|
|
const lru = createLru<string>({ max: 10, ttlMs: 60_000 })
|
|
let releaseOld: (v: string) => void = () => {}
|
|
const old = lru.load('/q', () => new Promise<string>((r) => (releaseOld = r)))
|
|
lru.clear()
|
|
const fresh = lru.load('/q', async () => 'new')
|
|
releaseOld('old')
|
|
assert.equal(await old, 'old')
|
|
assert.equal(await fresh, 'new')
|
|
assert.equal(lru.get('/q'), 'new')
|
|
})
|
|
|
|
test('lru: ttl 0 caches nothing but still shares a load in flight', async () => {
|
|
const lru = createLru<string>({ max: 10, ttlMs: 0 })
|
|
let calls = 0
|
|
const loader = async () => (calls++, 'v')
|
|
await Promise.all([lru.load('/z', loader), lru.load('/z', loader)])
|
|
assert.equal(calls, 1)
|
|
await lru.load('/z', loader)
|
|
assert.equal(calls, 2)
|
|
lru.set('/z', 'w')
|
|
assert.equal(lru.get('/z'), undefined)
|
|
})
|
|
|
|
test('lru: a loader that throws at once rejects and leaves nothing in flight', async () => {
|
|
const lru = createLru<string>({ max: 10, ttlMs: 60_000 })
|
|
const boom = (() => {
|
|
throw new Error('sync')
|
|
}) as unknown as () => Promise<string>
|
|
await assert.rejects(lru.load('/s', boom), /sync/)
|
|
assert.equal(await lru.load('/s', async () => 'after'), 'after')
|
|
})
|