import { describe, expect, it, vi } from 'vitest'; import { createFetchHttpClient } from '../src/http/types.js'; type FetchImpl = typeof fetch; function okResponse(body: string, status = 200): Response { return new Response(body, { status, headers: { 'content-type': 'application/json' } }); } describe('createFetchHttpClient', () => { it('rejects a non-positive timeout at construction', () => { expect(() => createFetchHttpClient({ timeoutMs: 0 })).toThrow(/timeoutMs/); }); it('attaches an AbortSignal deadline to every request and parses JSON bodies', async () => { const fetchImpl = vi.fn().mockResolvedValue(okResponse('{"arenaSessionId":"s1"}')); const client = createFetchHttpClient({ timeoutMs: 30_000, fetchImpl }); const res = await client.request({ method: 'POST', url: 'https://arena.invalid/v1/login', body: { a: 1 } }); expect(res).toEqual({ status: 200, body: { arenaSessionId: 's1' } }); const init = fetchImpl.mock.calls[0]?.[1]; expect(init?.signal).toBeInstanceOf(AbortSignal); expect(init?.signal?.aborted).toBe(false); expect(init?.method).toBe('POST'); expect(init?.body).toBe('{"a":1}'); expect((init?.headers as Record)['Content-Type']).toBe('application/json'); }); it('rejects with a TimeoutError when the upstream does not answer within timeoutMs', async () => { // A fetch that only settles when its signal aborts — i.e. a hung socket. const fetchImpl: FetchImpl = (_url, init) => new Promise((_resolve, reject) => { init?.signal?.addEventListener('abort', () => reject(init.signal?.reason as Error)); }); const client = createFetchHttpClient({ timeoutMs: 20, fetchImpl }); await expect(client.request({ method: 'GET', url: 'https://arena.invalid/v1/items' })).rejects.toMatchObject({ name: 'TimeoutError', }); }); it('returns raw text when the body is not JSON and null when empty', async () => { const fetchImpl = vi .fn() .mockResolvedValueOnce(okResponse('not json', 502)) .mockResolvedValueOnce(new Response(null, { status: 204 })); const client = createFetchHttpClient({ timeoutMs: 1000, fetchImpl }); expect(await client.request({ method: 'GET', url: 'https://arena.invalid/x' })).toEqual({ status: 502, body: 'not json', }); expect(await client.request({ method: 'GET', url: 'https://arena.invalid/y' })).toEqual({ status: 204, body: null, }); }); });