import { fetchJsonWithTimeout } from '../fetch-with-timeout';

describe('fetchJsonWithTimeout', () => {
    const realFetch = global.fetch;
    afterEach(() => {
        global.fetch = realFetch;
        jest.useRealTimers();
    });

    it('rejects when the request outlives the timeout (no infinite blank hang)', async () => {
        jest.useFakeTimers();
        // A fetch that never resolves on its own but honors the abort signal —
        // mirrors a wedged /api/reports/executive build.
        global.fetch = jest.fn((_url: string, opts: { signal: AbortSignal }) =>
            new Promise((_resolve, reject) => {
                opts.signal.addEventListener('abort', () =>
                    reject(new DOMException('aborted', 'AbortError')),
                );
            }),
        ) as unknown as typeof fetch;

        const p = fetchJsonWithTimeout('/api/reports/executive', 15000);
        const rejects = expect(p).rejects.toThrow();
        jest.advanceTimersByTime(15000);
        await rejects;
    });

    it('unwraps { metrics } on success', async () => {
        global.fetch = jest.fn(async () => ({
            ok: true,
            json: async () => ({ success: true, metrics: { overview: { totalMonitors: 3 } } }),
        })) as unknown as typeof fetch;

        await expect(fetchJsonWithTimeout('/x', 15000)).resolves.toEqual({
            overview: { totalMonitors: 3 },
        });
    });

    it('throws on a non-ok response', async () => {
        global.fetch = jest.fn(async () => ({ ok: false, status: 503 })) as unknown as typeof fetch;
        await expect(fetchJsonWithTimeout('/x', 15000)).rejects.toThrow('503');
    });
});
