/** * API Client for Integration Tests * Provides typed HTTP client for testing API endpoints */ export interface ApiClientConfig { baseUrl: string; timeout?: number; } export class ApiClient { private baseUrl: string; private timeout: number; constructor(config: ApiClientConfig) { this.baseUrl = config.baseUrl.replace(/\/$/, ''); // Remove trailing slash this.timeout = config.timeout || 30000; } /** * Make HTTP request to API */ private async request(method: string, path: string, body?: unknown, headers: Record = {}): Promise { const url = `${this.baseUrl}${path}`; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); try { const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json', ...headers, }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { const errorText = await response.text(); throw new Error(`API Error ${response.status}: ${errorText}`); } const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return (await response.json()) as T; } return (await response.text()) as unknown as T; } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error(`Request timeout after ${this.timeout}ms`); } throw error; } } // GET requests async get(path: string, headers?: Record): Promise { return this.request('GET', path, undefined, headers); } // POST requests async post(path: string, body: unknown, headers?: Record): Promise { return this.request('POST', path, body, headers); } // PUT requests async put(path: string, body: unknown, headers?: Record): Promise { return this.request('PUT', path, body, headers); } // PATCH requests async patch(path: string, body: unknown, headers?: Record): Promise { return this.request('PATCH', path, body, headers); } // DELETE requests async delete(path: string, headers?: Record): Promise { return this.request('DELETE', path, undefined, headers); } /** * Health check */ async health(): Promise { try { const response = await fetch(`${this.baseUrl}/health`); return response.ok; } catch { return false; } } /** * Wait for API to be ready */ async waitForReady(timeout: number = 60000): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeout) { if (await this.health()) { return; } await new Promise(resolve => setTimeout(resolve, 1000)); } throw new Error(`API failed to become ready within ${timeout}ms`); } }