feat(web): add api.{get,post,del} typed facade over apiFetch

This commit is contained in:
2026-04-22 16:04:25 -04:00
parent 4d1a7f8b93
commit 37e5539e5a
2 changed files with 33 additions and 1 deletions
+25 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, test, vi } from 'vitest';
import { apiFetch, type ApiError } from './client';
import { api, apiFetch, type ApiError } from './client';
afterEach(() => {
vi.unstubAllGlobals();
@@ -55,3 +55,27 @@ describe('apiFetch', () => {
});
});
});
describe('api.get/post/del', () => {
test('api.get returns typed JSON on 200', async () => {
stubFetch(200, { id: 'abc', name: 'A' });
type T = { id: string; name: string };
const result = await api.get<T>('/api/artists/abc');
expect(result).toEqual({ id: 'abc', name: 'A' });
});
test('api.post serializes body and sets method', async () => {
const spy = stubFetch(200, { ok: true });
await api.post('/api/auth/login', { username: 'u', password: 'p' });
const init = spy.mock.calls[0][1] as RequestInit;
expect(init.method).toBe('POST');
expect(init.body).toBe(JSON.stringify({ username: 'u', password: 'p' }));
});
test('api.del sends DELETE and resolves to null on 204', async () => {
const spy = stubFetch(204, null);
const result = await api.del('/api/sessions/xyz');
expect(spy.mock.calls[0][1]).toMatchObject({ method: 'DELETE' });
expect(result).toBeNull();
});
});
+8
View File
@@ -33,3 +33,11 @@ export async function apiFetch(path: string, init?: RequestInit): Promise<unknow
}
return body;
}
export const api = {
get: <T>(path: string): Promise<T> => apiFetch(path) as Promise<T>,
post: <T>(path: string, body: unknown): Promise<T> =>
apiFetch(path, { method: 'POST', body: JSON.stringify(body) }) as Promise<T>,
del: (path: string): Promise<null> =>
apiFetch(path, { method: 'DELETE' }) as Promise<null>
};