import { HttpClient } from './http-client'; /** * Regression tests for two bugs that only manifested in a browser, and so * survived every server-side and curl-based check. */ describe('HttpClient', () => { const okResponse = () => new Response(JSON.stringify({ success: true, data: { ok: true }, meta: {} }), { status: 200, headers: { 'content-type': 'application/json' }, }); it('calls the default fetch with the global receiver, not the client instance', async () => { // Browsers require `fetch` to be invoked with Window as `this`. Storing // `globalThis.fetch` on the instance and calling `this.fetchImpl(...)` // passes the HttpClient as the receiver and throws "Illegal invocation" — // in the browser only. Node does not care, which is precisely why every // server-side check and every curl passed while the browser was broken. const original = globalThis.fetch; const receivers: unknown[] = []; globalThis.fetch = function (this: unknown) { // Pushed rather than assigned to a local: capturing the receiver is the // point of the test, and a plain alias trips `no-this-alias`. receivers.push(this); return Promise.resolve(okResponse()); } as unknown as typeof fetch; try { // No fetchImpl — exercise the default path, which is the one that broke. const client = new HttpClient({ baseUrl: 'http://api.test' }); await client.get('/thing'); } finally { globalThis.fetch = original; } expect(receivers).toHaveLength(1); expect(receivers[0]).toBe(globalThis); expect(receivers[0]).not.toBeInstanceOf(HttpClient); }); it('does not retry a request that opted out, so refresh cannot recurse', async () => { // `onUnauthorized` refreshes by calling the refresh endpoint. If that call // is itself retryable, its own 401 triggers another refresh — an unbounded // loop that hammers the API from the browser. let calls = 0; let refreshes = 0; const fetchImpl = (() => { calls += 1; return Promise.resolve(new Response('{}', { status: 401 })); }) as unknown as typeof fetch; const client = new HttpClient({ baseUrl: 'http://api.test', fetchImpl, onUnauthorized: () => { refreshes += 1; return false; }, }); await expect( client.post('/auth/refresh', undefined, { skipAuthRetry: true }), ).rejects.toThrow(); expect(calls).toBe(1); expect(refreshes).toBe(0); }); it('still offers one retry for ordinary requests', async () => { let calls = 0; const fetchImpl = (() => { calls += 1; return Promise.resolve(calls === 1 ? new Response('{}', { status: 401 }) : okResponse()); }) as unknown as typeof fetch; const client = new HttpClient({ baseUrl: 'http://api.test', fetchImpl, onUnauthorized: () => true, }); await expect(client.get('/protected')).resolves.toEqual({ ok: true }); expect(calls).toBe(2); }); });