mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-09-23 19:47:08 -04:00
113 lines
3.1 KiB
TypeScript
113 lines
3.1 KiB
TypeScript
import { TEST_CONFIG } from '../test-config';
|
|
|
|
export type RawJson = unknown;
|
|
|
|
export interface ApiClientOptions {
|
|
baseUrl?: string;
|
|
token?: string;
|
|
}
|
|
|
|
/**
|
|
* Thin fetch wrapper used by every feature-area API module.
|
|
* One instance per test (or one shared admin client for setup).
|
|
*/
|
|
export class ApiClient {
|
|
baseUrl: string;
|
|
token?: string;
|
|
|
|
constructor(opts: ApiClientOptions = {}) {
|
|
this.baseUrl = opts.baseUrl ?? TEST_CONFIG.appUrl;
|
|
this.token = opts.token;
|
|
}
|
|
|
|
setToken(token: string | undefined): void {
|
|
this.token = token;
|
|
}
|
|
|
|
private buildHeaders(extra?: HeadersInit): HeadersInit {
|
|
const headers: Record<string, string> = {};
|
|
if (this.token) {
|
|
headers['Authorization'] = `Bearer ${this.token}`;
|
|
}
|
|
if (extra) {
|
|
const entries = extra instanceof Headers ? Array.from(extra.entries()) : Object.entries(extra as Record<string, string>);
|
|
for (const [k, v] of entries) {
|
|
headers[k] = v;
|
|
}
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
request(path: string, init: RequestInit = {}): Promise<Response> {
|
|
const url = path.startsWith('http') ? path : `${this.baseUrl}${path}`;
|
|
return fetch(url, {
|
|
...init,
|
|
headers: this.buildHeaders(init.headers),
|
|
});
|
|
}
|
|
|
|
async get(path: string): Promise<Response> {
|
|
return this.request(path);
|
|
}
|
|
|
|
async getJson<T = RawJson>(path: string): Promise<T> {
|
|
const res = await this.request(path);
|
|
if (!res.ok) {
|
|
throw new Error(`GET ${path} failed: ${res.status} ${await res.text()}`);
|
|
}
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
async post(path: string, body?: unknown): Promise<Response> {
|
|
return this.request(path, {
|
|
method: 'POST',
|
|
headers: body !== undefined ? { 'Content-Type': 'application/json' } : {},
|
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
});
|
|
}
|
|
|
|
async postJson<T = RawJson>(path: string, body?: unknown): Promise<T> {
|
|
const res = await this.post(path, body);
|
|
if (!res.ok) {
|
|
throw new Error(`POST ${path} failed: ${res.status} ${await res.text()}`);
|
|
}
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
async put(path: string, body?: unknown): Promise<Response> {
|
|
return this.request(path, {
|
|
method: 'PUT',
|
|
headers: body !== undefined ? { 'Content-Type': 'application/json' } : {},
|
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
});
|
|
}
|
|
|
|
async putJson<T = RawJson>(path: string, body?: unknown): Promise<T> {
|
|
const res = await this.put(path, body);
|
|
if (!res.ok) {
|
|
throw new Error(`PUT ${path} failed: ${res.status} ${await res.text()}`);
|
|
}
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
async delete(path: string): Promise<Response> {
|
|
return this.request(path, { method: 'DELETE' });
|
|
}
|
|
}
|
|
|
|
export async function waitForApp(client: ApiClient, timeoutMs = 90_000): Promise<void> {
|
|
const start = Date.now();
|
|
while (Date.now() - start < timeoutMs) {
|
|
try {
|
|
const res = await fetch(`${client.baseUrl}/health`);
|
|
if (res.ok) {
|
|
return;
|
|
}
|
|
} catch {
|
|
// not ready yet
|
|
}
|
|
await new Promise((r) => setTimeout(r, 2000));
|
|
}
|
|
throw new Error(`App did not become ready within ${timeoutMs}ms`);
|
|
}
|