Files
lunarfront-app/packages/backend/api-tests/lib/client.ts
Ryan Moon f47bfdbcdb Add standalone API test runner with accounts and members suites
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.
2026-03-28 12:52:04 -05:00

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