import { describe, expect, it, vi } from 'vitest'; import { ApiError, request } from '@/api/client'; function jsonResponse(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' }, }); } describe('api client', () => { it('returns the parsed payload on success', async () => { const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ api_version: 'v1' })); vi.stubGlobal('fetch', fetchMock); await expect(request<{ api_version: string }>('/meta')).resolves.toEqual({ api_version: 'v1', }); expect(fetchMock).toHaveBeenCalledWith( '/api/v1/meta', expect.objectContaining({ method: 'GET', credentials: 'same-origin' }), ); }); it('maps the server error envelope to ApiError', async () => { vi.stubGlobal( 'fetch', vi .fn() .mockResolvedValue( jsonResponse({ error: { code: 'perm.denied', message: 'no access' } }, 403), ), ); await expect(request('/guilds/1')).rejects.toMatchObject({ name: 'ApiError', code: 'perm.denied', status: 403, }); }); it('falls back to internal.error when the body is not an envelope', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue(new Response('gateway timeout', { status: 504 })), ); const error = await request('/meta').catch((reason: unknown) => reason); expect(error).toBeInstanceOf(ApiError); expect((error as ApiError).code).toBe('internal.error'); expect((error as ApiError).status).toBe(504); }); it('sends a json body for write requests', async () => { const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ ok: true })); vi.stubGlobal('fetch', fetchMock); await request('/guilds', { method: 'POST', body: { name: 'test' } }); const init = fetchMock.mock.calls[0]?.[1] as RequestInit; expect(init.body).toBe('{"name":"test"}'); expect((init.headers as Record)['Content-Type']).toBe('application/json'); }); });