import { afterEach, describe, expect, it, vi } from 'vitest' import { z } from 'zod' import { ApiClient } from '../../../src/api/client' describe('ApiClient', () => { afterEach(() => vi.restoreAllMocks()) it('handles empty 204 responses', async () => { vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(null, { status: 204 })) await expect(new ApiClient('/api', () => 'token').request('/logout', { method: 'POST' })).resolves.toBeUndefined() }) it('validates successful responses with zod', async () => { vi.spyOn(globalThis, 'fetch').mockResolvedValue(Response.json({ id: 'wrong' })) await expect( new ApiClient('/api', () => null).request('/me', { schema: z.object({ id: z.number() }) }), ).rejects.toMatchObject({ kind: 'validation' }) }) it('returns file responses without parsing them as JSON', async () => { const blob = new Blob(['# report'], { type: 'text/markdown' }) const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(blob)) await expect(new ApiClient('/api', () => 'token').request('/runs/1/export', { responseType: 'blob' })).resolves.toEqual(expect.any(Blob)) expect(new Headers(fetchMock.mock.calls[0][1]?.headers).get('Accept')).toBe('text/markdown') }) it('refreshes and replays a business request once after 401', async () => { let token = 'old-token' const refresh = vi.fn(async (force?: boolean) => { if (!force) return false token = 'new-token' return true }) const fetchMock = vi.spyOn(globalThis, 'fetch') .mockResolvedValueOnce(Response.json({ error: { message: '需要有效的访问令牌' } }, { status: 401 })) .mockResolvedValueOnce(Response.json({ ok: true })) await expect(new ApiClient('/api', () => token, refresh).request('/runs/1')).resolves.toEqual({ ok: true }) expect(refresh).toHaveBeenNthCalledWith(1, false) expect(refresh).toHaveBeenCalledWith(true) expect(fetchMock).toHaveBeenCalledTimes(2) expect(new Headers(fetchMock.mock.calls[1][1]?.headers).get('Authorization')).toBe('Bearer new-token') }) it('does not refresh or replay a request more than once', async () => { const refresh = vi.fn().mockImplementation(async (force?: boolean) => Boolean(force)) vi.spyOn(globalThis, 'fetch').mockResolvedValue( Response.json({ error: { message: '需要有效的访问令牌' } }, { status: 401 }), ) await expect(new ApiClient('/api', () => 'token', refresh).request('/me')).rejects.toMatchObject({ kind: 'authentication' }) expect(refresh).toHaveBeenCalledTimes(2) expect(refresh).toHaveBeenLastCalledWith(true) expect(fetch).toHaveBeenCalledTimes(2) }) })