feat(ui): client API typé (list, CRUD, random, thèmes) avec gestion d'erreurs
This commit is contained in:
1 parent
de00a827ed
commit
405ce6e603
2 files changed
+152
No files matched your search
@@ -0,0 +1,105 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ApiClientError, cardsApi } from './api-client'
|
||||
|
||||
const CARD = {
|
||||
id: '10000000-0000-4000-8000-000000000001',
|
||||
question: 'Q ?',
|
||||
response: 'R',
|
||||
theme: 'git',
|
||||
tags: ['a'],
|
||||
createdAt: '2026-01-01T08:00:00.000Z',
|
||||
updatedAt: '2026-01-01T08:00:00.000Z',
|
||||
}
|
||||
|
||||
const jsonResponse = (body: unknown, status = 200) =>
|
||||
new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } })
|
||||
|
||||
const fetchMock = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = fetchMock as unknown as typeof fetch
|
||||
fetchMock.mockReset()
|
||||
})
|
||||
|
||||
function expectRequest(method: string, path: string) {
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
path,
|
||||
expect.objectContaining({ method }),
|
||||
)
|
||||
}
|
||||
|
||||
describe('cardsApi', () => {
|
||||
it('list() appelle GET /api/cards', async () => {
|
||||
fetchMock.mockResolvedValue(jsonResponse([CARD]))
|
||||
await expect(cardsApi.list()).resolves.toEqual([CARD])
|
||||
expectRequest('GET', '/api/cards')
|
||||
})
|
||||
|
||||
it('get() appelle GET /api/cards/:id', async () => {
|
||||
fetchMock.mockResolvedValue(jsonResponse(CARD))
|
||||
await expect(cardsApi.get(CARD.id)).resolves.toEqual(CARD)
|
||||
expectRequest('GET', `/api/cards/${CARD.id}`)
|
||||
})
|
||||
|
||||
it('create() envoie POST avec un corps JSON', async () => {
|
||||
fetchMock.mockResolvedValue(jsonResponse(CARD, 201))
|
||||
await expect(cardsApi.create({ question: 'Q ?', response: 'R', theme: 'git' } as never)).resolves.toEqual(CARD)
|
||||
const [_, init] = fetchMock.mock.calls[0]
|
||||
expect(init.method).toBe('POST')
|
||||
expect(JSON.parse(init.body)).toEqual({ question: 'Q ?', response: 'R', theme: 'git' })
|
||||
})
|
||||
|
||||
it('update() envoie PUT /api/cards/:id', async () => {
|
||||
fetchMock.mockResolvedValue(jsonResponse(CARD))
|
||||
await expect(cardsApi.update(CARD.id, { question: 'Q ?' } as never)).resolves.toEqual(CARD)
|
||||
expectRequest('PUT', `/api/cards/${CARD.id}`)
|
||||
})
|
||||
|
||||
it('remove() envoie DELETE et ignore le corps', async () => {
|
||||
fetchMock.mockResolvedValue(new Response(null, { status: 204 }))
|
||||
await expect(cardsApi.remove(CARD.id)).resolves.toBeUndefined()
|
||||
expectRequest('DELETE', `/api/cards/${CARD.id}`)
|
||||
})
|
||||
|
||||
it('random() sans thème et avec thème (URL-encodé)', async () => {
|
||||
fetchMock.mockResolvedValue(jsonResponse(CARD))
|
||||
await cardsApi.random()
|
||||
expectRequest('GET', '/api/cards/random')
|
||||
await cardsApi.random('git ops')
|
||||
expectRequest('GET', '/api/cards/random?theme=git%20ops')
|
||||
})
|
||||
|
||||
it('themes() appelle GET /api/themes', async () => {
|
||||
fetchMock.mockResolvedValue(jsonResponse(['git', 'docker']))
|
||||
await expect(cardsApi.themes()).resolves.toEqual(['git', 'docker'])
|
||||
expectRequest('GET', '/api/themes')
|
||||
})
|
||||
|
||||
it('lève ApiClientError avec le message de l API sur une erreur 404', async () => {
|
||||
fetchMock.mockResolvedValue(jsonResponse({ error: 'NotFoundError', message: 'Carte inconnue' }, 404))
|
||||
await expect(cardsApi.get('nope')).rejects.toMatchObject({
|
||||
name: 'NotFoundError',
|
||||
message: 'Carte inconnue',
|
||||
status: 404,
|
||||
})
|
||||
})
|
||||
|
||||
it('lève ApiClientError sur une réponse invalide', async () => {
|
||||
fetchMock.mockResolvedValue(jsonResponse({ error: 'ValidationError', message: 'Payload invalide' }, 400))
|
||||
await expect(cardsApi.create({} as never)).rejects.toBeInstanceOf(ApiClientError)
|
||||
})
|
||||
|
||||
it('lève ApiClientError NetworkError quand fetch échoue', async () => {
|
||||
fetchMock.mockRejectedValue(new TypeError('network'))
|
||||
await expect(cardsApi.list()).rejects.toMatchObject({ name: 'NetworkError', status: 0 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('ApiClientError', () => {
|
||||
it('porte status, name et message', () => {
|
||||
const err = new ApiClientError(404, 'NotFoundError', 'inconnu')
|
||||
expect(err.status).toBe(404)
|
||||
expect(err.name).toBe('NotFoundError')
|
||||
expect(err.message).toBe('inconnu')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user