import { describe, it, expect, vi, beforeEach } from 'vitest' // We need to mock fetch before importing the module, // and also need to handle import.meta.env used by the module. // Mock global fetch const mockFetch = vi.fn() vi.stubGlobal('fetch', mockFetch) // Dynamic import so our fetch stub is in place const api = await import('../api') beforeEach(() => { mockFetch.mockReset() }) describe('fetchCorpora', () => { it('calls the correct endpoint', async () => { const mockCorpora = [ { id: '1', slug: 'test', title: 'Test', profile_id: 'medieval-illuminated', created_at: '', updated_at: '' }, ] mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockCorpora), }) const result = await api.fetchCorpora() expect(mockFetch).toHaveBeenCalledOnce() expect(mockFetch).toHaveBeenCalledWith('/api/v1/corpora') expect(result).toEqual(mockCorpora) }) it('throws ApiError on non-200 response', async () => { mockFetch.mockResolvedValueOnce({ ok: false, status: 404, json: () => Promise.resolve(null), }) await expect(api.fetchCorpora()).rejects.toThrow(api.ApiError) await mockFetch.mockResolvedValueOnce({ ok: false, status: 500, json: () => Promise.resolve(null), }) await expect(api.fetchCorpora()).rejects.toThrow('HTTP 500') }) }) describe('ApiError', () => { it('has correct name and status', () => { const err = new api.ApiError(422, 'Validation failed') expect(err.name).toBe('ApiError') expect(err.status).toBe(422) expect(err.message).toBe('Validation failed') expect(err).toBeInstanceOf(Error) }) }) describe('post helper — error extraction', () => { it('extracts string detail from FastAPI error', async () => { mockFetch.mockResolvedValueOnce({ ok: false, status: 400, json: () => Promise.resolve({ detail: 'Corpus not found' }), }) await expect( api.createCorpus({ slug: 'x', title: 'X', profile_id: 'p' }) ).rejects.toThrow('Corpus not found') }) it('extracts array detail from FastAPI validation error', async () => { mockFetch.mockResolvedValueOnce({ ok: false, status: 422, json: () => Promise.resolve({ detail: [ { loc: ['body', 'slug'], msg: 'field required', type: 'missing' }, ], }), }) await expect( api.createCorpus({ slug: '', title: '', profile_id: '' }) ).rejects.toThrow('body \u2192 slug : field required') }) it('falls back to HTTP status when json parsing fails', async () => { mockFetch.mockResolvedValueOnce({ ok: false, status: 500, json: () => Promise.reject(new Error('not json')), }) await expect( api.createCorpus({ slug: 'x', title: 'X', profile_id: 'p' }) ).rejects.toThrow('HTTP 500') }) }) describe('deleteCorpus', () => { it('calls DELETE on the correct endpoint', async () => { mockFetch.mockResolvedValueOnce({ ok: true }) await api.deleteCorpus('abc-123') expect(mockFetch).toHaveBeenCalledWith('/api/v1/corpora/abc-123', { method: 'DELETE', }) }) })