import { describe, expect, it } from 'vitest' import { Upstream } from './upstream.ts' import type { HubConfig } from './config.ts' const server = { id: 'a', url: 'http://a.test', token: 't' } const config = { cacheSeconds: 15, timeoutMs: 20 } as HubConfig const ok = () => new Response(JSON.stringify({ status: 'ok', response: { n: 1 } }), { headers: { 'content-type': 'application/json' } }) describe('Upstream', () => { it('shares one request among reads asked at the same time', async () => { let calls = 0 const up = new Upstream(config, (async () => { calls++ return ok() }) as typeof fetch) await Promise.all([up.call(server, 'x'), up.call(server, 'x'), up.call(server, 'x')]) expect(calls).toBe(1) }) it('names the HTTP status when a proxy answers with a page', async () => { const up = new Upstream(config, (async () => new Response('Bad Gateway', { status: 502, statusText: 'Bad Gateway', headers: { 'content-type': 'text/plain' } })) as typeof fetch) await expect(up.call(server, 'x')).rejects.toThrow('HTTP 502 Bad Gateway') }) it('after a timeout, fails reads of that server at once for a while', async () => { let now = 0 let calls = 0 const hang = ((_url: string, init: RequestInit) => { calls++ return new Promise((_resolve, reject) => init.signal?.addEventListener('abort', () => reject(Object.assign(new Error('aborted'), { name: 'AbortError' })))) }) as unknown as typeof fetch const up = new Upstream(config, hang, () => now) await expect(up.call(server, 'x')).rejects.toThrow('timed out after 20 ms') await expect(up.call(server, 'y')).rejects.toThrow('timed out') // Two: the read and its second try. expect(calls).toBe(2) now = 11_000 await expect(up.call(server, 'y')).rejects.toThrow('timed out') expect(calls).toBe(4) }) it('asks a slow read again and takes the answer that comes first', async () => { let calls = 0 const up = new Upstream(config, ((_url: string, init: RequestInit) => { calls++ if (calls > 1) return Promise.resolve(ok()) return new Promise((_resolve, reject) => init.signal?.addEventListener('abort', () => reject(Object.assign(new Error('aborted'), { name: 'AbortError' })))) }) as unknown as typeof fetch) await expect(up.call(server, 'x')).resolves.toEqual({ n: 1 }) expect(calls).toBe(2) }) it('serves the last good answer for two minutes when the server stops answering', async () => { let now = 0 let fail = false const up = new Upstream(config, (async () => { if (fail) throw new Error('ECONNRESET') return ok() }) as typeof fetch, () => now) await up.call(server, 'x') fail = true now = 60_000 await expect(up.call(server, 'x')).resolves.toEqual({ n: 1 }) now = 140_000 await expect(up.call(server, 'x')).rejects.toThrow('ECONNRESET') }) })