import { strictEqual, rejects } from 'node:assert/strict'; import test from 'node:test'; import { cacheableFetch } from './functions'; type FetchMock = typeof fetch; const withFixedNow = async (now: number, fn: () => Promise) => { const originalNow = Date.now; Date.now = () => now; try { return await fn(); } finally { Date.now = originalNow; } }; test('cacheableFetch reuses cached response before expiry', async () => { const originalFetch = globalThis.fetch; const fixedNow = Date.now(); const futureExpires = new Date(fixedNow + 60_000).toUTCString(); let calls = 0; globalThis.fetch = (async () => { calls += 1; return new Response(JSON.stringify({ ok: true, call: calls }), { status: 200, headers: { Expires: futureExpires } }); }) as FetchMock; try { await withFixedNow(fixedNow, async () => { const first = await cacheableFetch<{ ok: boolean; call: number }>('https://example.test/cache-1'); const second = await cacheableFetch<{ ok: boolean; call: number }>('https://example.test/cache-1'); const third = await cacheableFetch<{ ok: boolean; call: number }>('https://example.test/cache-1'); strictEqual(calls, 1); strictEqual(first.call, 1); strictEqual(second.call, 1); strictEqual(third.call, 1); }); } finally { globalThis.fetch = originalFetch; } }); test('cacheableFetch does not share cache across different urls', async () => { const originalFetch = globalThis.fetch; const fixedNow = Date.now(); const futureExpires = new Date(fixedNow + 60_000).toUTCString(); let calls = 0; globalThis.fetch = (async () => { calls += 1; return new Response(JSON.stringify({ ok: true, call: calls }), { status: 200, headers: { Expires: futureExpires } }); }) as FetchMock; try { await withFixedNow(fixedNow, async () => { const first = await cacheableFetch<{ ok: boolean; call: number }>('https://example.test/cache-4'); const second = await cacheableFetch<{ ok: boolean; call: number }>('https://example.test/cache-5'); strictEqual(calls, 2); strictEqual(first.call, 1); strictEqual(second.call, 2); }); } finally { globalThis.fetch = originalFetch; } }); test('cacheableFetch refetches after expiry', async () => { const originalFetch = globalThis.fetch; const fixedNow = Date.now(); const pastExpires = new Date(fixedNow - 60_000).toUTCString(); let calls = 0; globalThis.fetch = (async () => { calls += 1; return new Response(JSON.stringify({ ok: true, call: calls }), { status: 200, headers: { Expires: pastExpires } }); }) as FetchMock; try { await withFixedNow(fixedNow, async () => { const first = await cacheableFetch<{ ok: boolean; call: number }>('https://example.test/cache-2'); const second = await cacheableFetch<{ ok: boolean; call: number }>('https://example.test/cache-2'); strictEqual(calls, 2); strictEqual(first.call, 1); strictEqual(second.call, 2); }); } finally { globalThis.fetch = originalFetch; } }); test('cacheableFetch does not cache failed responses', async () => { const originalFetch = globalThis.fetch; const fixedNow = Date.now(); const futureExpires = new Date(fixedNow + 60_000).toUTCString(); let calls = 0; globalThis.fetch = (async () => { calls += 1; if (calls === 1) { return new Response(JSON.stringify({ error: 'boom' }), { status: 500, headers: { Expires: futureExpires } }); } return new Response(JSON.stringify({ ok: true, call: calls }), { status: 200, headers: { Expires: futureExpires } }); }) as FetchMock; try { await withFixedNow(fixedNow, async () => { await rejects(() => cacheableFetch('https://example.test/cache-3')); const result = await cacheableFetch<{ ok: boolean; call: number }>('https://example.test/cache-3'); strictEqual(calls, 2); strictEqual(result.call, 2); }); } finally { globalThis.fetch = originalFetch; } });