Custom test framework that starts the backend, creates a test DB, runs migrations, and hits real HTTP endpoints. Supports --suite and --tag filtering. 24 tests covering account CRUD, member inheritance, state normalization, move, search, and auto-generated numbers. Run with bun run api-test.
47 lines
1.7 KiB
TypeScript
47 lines
1.7 KiB
TypeScript
export interface ApiResponse<T = unknown> {
|
|
status: number
|
|
data: T
|
|
ok: boolean
|
|
}
|
|
|
|
export interface ApiClient {
|
|
get<T = unknown>(path: string, params?: Record<string, unknown>): Promise<ApiResponse<T>>
|
|
post<T = unknown>(path: string, body?: unknown): Promise<ApiResponse<T>>
|
|
patch<T = unknown>(path: string, body?: unknown): Promise<ApiResponse<T>>
|
|
del<T = unknown>(path: string): Promise<ApiResponse<T>>
|
|
}
|
|
|
|
function buildQueryString(params?: Record<string, unknown>): string {
|
|
if (!params) return ''
|
|
const sp = new URLSearchParams()
|
|
for (const [k, v] of Object.entries(params)) {
|
|
if (v !== undefined && v !== null && v !== '') sp.set(k, String(v))
|
|
}
|
|
const qs = sp.toString()
|
|
return qs ? `?${qs}` : ''
|
|
}
|
|
|
|
export function createClient(baseUrl: string, token?: string): ApiClient {
|
|
async function request<T>(method: string, path: string, body?: unknown): Promise<ApiResponse<T>> {
|
|
const headers: Record<string, string> = {}
|
|
if (body !== undefined) headers['Content-Type'] = 'application/json'
|
|
if (token) headers['Authorization'] = `Bearer ${token}`
|
|
|
|
const res = await fetch(`${baseUrl}${path}`, {
|
|
method,
|
|
headers,
|
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
})
|
|
|
|
const data = await res.json() as T
|
|
return { status: res.status, data, ok: res.ok }
|
|
}
|
|
|
|
return {
|
|
get: <T>(path: string, params?: Record<string, unknown>) => request<T>('GET', `${path}${buildQueryString(params)}`),
|
|
post: <T>(path: string, body?: unknown) => request<T>('POST', path, body),
|
|
patch: <T>(path: string, body?: unknown) => request<T>('PATCH', path, body),
|
|
del: <T>(path: string) => request<T>('DELETE', path),
|
|
}
|
|
}
|