import { afterEach, describe, expect, test, vi } from 'vitest'; import { fetchRemoteData, RemoteFetchError } from './remote-data'; /** A minimal Response stand-in for the bits the adapter reads. */ function fakeResponse(opts: { ok: boolean; status?: number; statusText?: string; body?: string; contentType?: string | null; }): Response { return { ok: opts.ok, status: opts.status ?? (opts.ok ? 200 : 500), statusText: opts.statusText ?? '', text: () => Promise.resolve(opts.body ?? ''), headers: { get: (k: string) => (k.toLowerCase() === 'content-type' ? (opts.contentType ?? null) : null), }, } as unknown as Response; } afterEach(() => vi.unstubAllGlobals()); describe('fetchRemoteData', () => { test('returns the body and content-type on success', async () => { vi.stubGlobal( 'fetch', vi.fn(() => Promise.resolve(fakeResponse({ ok: true, body: 'a,b\n1,2', contentType: 'text/csv' })), ), ); const result = await fetchRemoteData('https://example.com/data.csv'); expect(result.text).toBe('a,b\n1,2'); expect(result.contentType).toBe('text/csv'); }); test('classifies a non-2xx response as http with its status', async () => { vi.stubGlobal( 'fetch', vi.fn(() => Promise.resolve(fakeResponse({ ok: false, status: 404, statusText: 'Not Found' })), ), ); await expect(fetchRemoteData('https://example.com/missing.csv')).rejects.toMatchObject({ reason: 'http', status: 404, }); }); test('classifies a rejected fetch (CORS / offline) as network', async () => { vi.stubGlobal( 'fetch', vi.fn(() => Promise.reject(new TypeError('Failed to fetch'))), ); await expect(fetchRemoteData('https://blocked.example/data.csv')).rejects.toMatchObject({ reason: 'network', }); }); test('classifies an empty body as empty', async () => { vi.stubGlobal( 'fetch', vi.fn(() => Promise.resolve(fakeResponse({ ok: true, body: ' \n' }))), ); await expect(fetchRemoteData('https://example.com/blank.csv')).rejects.toMatchObject({ reason: 'empty', }); }); test('classifies an aborted fetch as timeout', async () => { vi.stubGlobal( 'fetch', vi.fn(() => { const e = new Error('aborted'); e.name = 'AbortError'; return Promise.reject(e); }), ); await expect( fetchRemoteData('https://slow.example/data.csv', { timeoutMs: 1 }), ).rejects.toMatchObject({ reason: 'timeout', }); }); test('the thrown error is a RemoteFetchError', async () => { vi.stubGlobal( 'fetch', vi.fn(() => Promise.reject(new TypeError('Failed to fetch'))), ); await expect(fetchRemoteData('https://x.example')).rejects.toBeInstanceOf(RemoteFetchError); }); });