buckets/frontend/src/lib/api.test.ts

112 lines
3.3 KiB
TypeScript

import { http, HttpResponse } from 'msw'
import { describe, expect, it } from 'vitest'
import { server } from '../test/server'
import { ApiError, api } from './api'
describe('api.postJson', () => {
it('sends application/json and returns the parsed body', async () => {
let sentContentType: string | null = null
server.use(
http.post('http://localhost/api/login', async ({ request }) => {
sentContentType = request.headers.get('Content-Type')
return HttpResponse.json({ id: 'u1', email: 'a@b.com' })
}),
)
const result = await api.postJson<{ id: string; email: string }>('/login', {
username: 'a@b.com',
password: 'secret',
})
expect(sentContentType).toBe('application/json')
expect(result).toEqual({ id: 'u1', email: 'a@b.com' })
})
it('throws ApiError on a non-2xx response', async () => {
server.use(
http.post('http://localhost/api/login', () =>
HttpResponse.json({ message: 'bad creds' }, { status: 401 }),
),
)
const error = await api.postJson('/login', {}).catch((e: unknown) => e)
expect(error).toBeInstanceOf(ApiError)
expect((error as ApiError).status).toBe(401)
})
})
describe('api path guard', () => {
it('throws if a path already includes the /api prefix', async () => {
// Callers pass `/login`, not `/api/login` — the client prepends BASE.
await expect(api.get('/api/login')).rejects.toThrow(/must not include/i)
})
})
describe('api.get', () => {
it('sends Accept: application/ld+json and returns the parsed body', async () => {
let accept: string | null = null
server.use(
http.get('http://localhost/api/scenarios', ({ request }) => {
accept = request.headers.get('Accept')
return HttpResponse.json({ member: [] })
}),
)
const result = await api.get<{ member: unknown[] }>('/scenarios')
expect(accept).toBe('application/ld+json')
expect(result).toEqual({ member: [] })
})
})
describe('api.post', () => {
it('sends Content-Type: application/ld+json (API Platform write format)', async () => {
let contentType: string | null = null
server.use(
http.post('http://localhost/api/buckets', async ({ request }) => {
contentType = request.headers.get('Content-Type')
return HttpResponse.json({ '@id': '/api/buckets/1' }, { status: 201 })
}),
)
await api.post('/buckets', { name: 'Rent' })
expect(contentType).toBe('application/ld+json')
})
})
describe('api.patch', () => {
it('sends Content-Type: application/merge-patch+json', async () => {
let contentType: string | null = null
server.use(
http.patch('http://localhost/api/buckets/1', async ({ request }) => {
contentType = request.headers.get('Content-Type')
return HttpResponse.json({ '@id': '/api/buckets/1' })
}),
)
await api.patch('/buckets/1', { name: 'Rent' })
expect(contentType).toBe('application/merge-patch+json')
})
})
describe('api.delete', () => {
it('resolves to undefined on a 204 No Content', async () => {
server.use(
http.delete(
'http://localhost/api/buckets/1',
() => new HttpResponse(null, { status: 204 }),
),
)
const result = await api.delete('/buckets/1')
expect(result).toBeUndefined()
})
})