import { afterEach, describe, expect, it, vi } from 'vitest' import { ApiError, apiFetch } from '../src/api/client' /* fetch is stubbed with Response-shaped objects — the client only needs status/ok/json, and this keeps the test independent of jsdom's fetch. */ function stubFetch(body: unknown, status = 200) { const mock = vi.fn().mockResolvedValue({ status, ok: status >= 200 && status < 300, statusText: 'status text', json: () => Promise.resolve(body), }) vi.stubGlobal('fetch', mock) return mock } afterEach(() => { vi.unstubAllGlobals() }) describe('apiFetch', () => { it('decodes a JSON response', async () => { stubFetch([{ id: 1, title: 'First' }]) await expect(apiFetch('/api/v1/items')).resolves.toEqual([{ id: 1, title: 'First' }]) }) it('sends method, JSON headers and a JSON body', async () => { const mock = stubFetch({ id: 1 }) await apiFetch('/api/v1/items', { method: 'POST', body: JSON.stringify({ title: 'x' }) }) const [path, init] = mock.mock.calls[0]! as [string, RequestInit] expect(path).toBe('/api/v1/items') expect(init.method).toBe('POST') expect(init.body).toBe(JSON.stringify({ title: 'x' })) expect(init.headers).toMatchObject({ 'Content-Type': 'application/json' }) }) it('returns undefined for 204 responses', async () => { stubFetch(null, 204) await expect(apiFetch('/api/v1/items/1', { method: 'DELETE' })).resolves.toBeUndefined() }) it('turns the backend error envelope into an ApiError', async () => { stubFetch({ error: { code: 'validation_failed', message: 'title: required' } }, 400) const err = await apiFetch('/api/v1/items', { method: 'POST' }).catch(e => e) expect(err).toBeInstanceOf(ApiError) expect(err).toMatchObject({ status: 400, code: 'validation_failed', message: 'title: required', }) }) it('falls back to statusText when the body is not the envelope', async () => { stubFetch('gateway timeout', 504) const err = await apiFetch('/api/v1/items').catch(e => e) expect(err).toMatchObject({ status: 504, code: 'unknown', message: 'status text' }) }) it('wraps network failures (backend down) as a network ApiError', async () => { vi.stubGlobal( 'fetch', vi.fn().mockRejectedValue(new TypeError('fetch failed')), ) const err = await apiFetch('/api/v1/items').catch(e => e) expect(err).toBeInstanceOf(ApiError) expect(err.code).toBe('network') expect(err.message).toContain('Go server') }) })