import { afterEach, describe, expect, it, vi } from 'vitest'; import { apiFetch, setApiBase } from './http'; describe('API contract preflight', () => { afterEach(() => { setApiBase('/api/v1'); vi.unstubAllGlobals(); }); it('blocks API requests when the server has a newer breaking major', async () => { const fetchMock = vi.fn(async () => new Response(JSON.stringify({ contract_version: '7.0.0' }), { status: 200 })); vi.stubGlobal('fetch', fetchMock); setApiBase('http://api.test/api/v1'); await expect(apiFetch('/channels')).rejects.toThrow('api_contract_mismatch'); expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith('http://api.test/api/v1/info', expect.any(Object)); }); it('checks the contract once and then sends the API request', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(new Response(JSON.stringify({ contract_version: '6.0.0' }), { status: 200 })) .mockResolvedValueOnce(new Response('{}', { status: 200 })) .mockResolvedValueOnce(new Response('{}', { status: 200 })); vi.stubGlobal('fetch', fetchMock); setApiBase('http://api.test/api/v1'); await expect(apiFetch('/channels')).resolves.toBeInstanceOf(Response); await expect(apiFetch('/shares')).resolves.toBeInstanceOf(Response); expect(fetchMock).toHaveBeenCalledTimes(3); }); });