Files
ai-safety-console-web/tests/unit/api/client.test.ts
T
2026-07-19 00:29:06 +08:00

57 lines
2.6 KiB
TypeScript

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)
})
})