Files
astrolabe/src/app/infrastructure/remote-data.test.ts
T

95 lines
2.8 KiB
TypeScript

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);
});
});