import { describe, it, expect, vi, beforeEach, afterEach, type MockInstance } from 'vitest'; import { listAuthors, getAuthor, listAuthorMangas } from './authors'; function ok(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } }); } function envelope(status: number, code: string, message: string): Response { return new Response(JSON.stringify({ error: { code, message } }), { status, headers: { 'content-type': 'application/json' } }); } describe('authors api client', () => { let fetchSpy: MockInstance; beforeEach(() => { fetchSpy = vi.spyOn(globalThis, 'fetch'); }); afterEach(() => { vi.restoreAllMocks(); }); it('listAuthors GETs /v1/authors with no params by default', async () => { fetchSpy.mockResolvedValueOnce(ok([])); await listAuthors(); const url = fetchSpy.mock.calls[0][0] as string; expect(url).toMatch(/\/v1\/authors$/); }); it('listAuthors encodes search, limit, offset', async () => { fetchSpy.mockResolvedValueOnce(ok([])); await listAuthors({ search: 'miura', limit: 5, offset: 0 }); const url = fetchSpy.mock.calls[0][0] as string; expect(url).toContain('search=miura'); expect(url).toContain('limit=5'); expect(url).toContain('offset=0'); }); it('getAuthor returns the AuthorWithCount shape', async () => { fetchSpy.mockResolvedValueOnce( ok({ id: 'a1', name: 'Kentaro Miura', created_at: '2026-01-01T00:00:00Z', manga_count: 3 }) ); const a = await getAuthor('a1'); expect(a.name).toBe('Kentaro Miura'); expect(a.manga_count).toBe(3); }); it('getAuthor surfaces 404 as ApiError', async () => { fetchSpy.mockResolvedValueOnce(envelope(404, 'not_found', 'not found')); await expect(getAuthor('missing')).rejects.toMatchObject({ status: 404, code: 'not_found' }); }); it('listAuthorMangas hits the nested route and forwards pagination', async () => { fetchSpy.mockResolvedValueOnce( ok({ items: [ { id: 'm1', title: 'Berserk', status: 'ongoing', alt_titles: [], description: null, cover_image_path: null, created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z' } ], page: { limit: 50, offset: 0, total: 1 } }) ); const result = await listAuthorMangas('a1', { limit: 20, offset: 10 }); expect(result.items[0].title).toBe('Berserk'); const url = fetchSpy.mock.calls[0][0] as string; expect(url).toMatch(/\/v1\/authors\/a1\/mangas\?/); expect(url).toContain('limit=20'); expect(url).toContain('offset=10'); }); });