// apps/web/e2e/fixtures/api-client.ts import type { PatientData, DoctorData, VitalSignsData, ScheduleData, AppointmentData, FollowUpTemplateData, FollowUpTaskData, AlertRuleData, } from './test-data'; const API_BASE = process.env.E2E_API_URL || 'http://localhost:3000/api/v1'; interface ApiResponse { success: boolean; data: T } interface Versioned { id: string; version: number } type VEntity = T & Versioned; interface LoginResponse { access_token: string; refresh_token: string; expires_in: number; user: { id: string; username: string; display_name: string; roles: string[] }; } export class ApiClient { private token = ''; async login(username?: string, password?: string): Promise { const res = await this.rawPost<{ success: boolean; data: LoginResponse }>( '/auth/login', { username: username || process.env.E2E_ADMIN_USER || 'admin', password: password || process.env.E2E_ADMIN_PASS || 'Admin@2026', }, ); this.token = res.data.access_token; return res.data; } async loginAsAdmin(): Promise { return this.login(); } getToken(): string { return this.token; } async createPatient(overrides?: Partial): Promise>> { return this.post('/health/patients', overrides ?? {}); } async updatePatient(id: string, version: number, data: Partial): Promise>> { return this.put(`/health/patients/${id}`, { ...data, version }); } async deletePatient(id: string, version: number): Promise { await this.del(`/health/patients/${id}`, { version }); } async createDoctor(overrides?: Partial): Promise>> { return this.post('/health/doctors', overrides ?? {}); } async deleteDoctor(id: string, version: number): Promise { await this.del(`/health/doctors/${id}`, { version }); } async createVitalSigns(patientId: string, overrides?: Partial): Promise>> { return this.post(`/health/patients/${patientId}/vital-signs`, overrides ?? {}); } async deleteVitalSigns(patientId: string, id: string, version: number): Promise { await this.del(`/health/patients/${patientId}/vital-signs/${id}`, { version }); } async createSchedule(overrides: ScheduleData): Promise>> { return this.post('/health/doctor-schedules', overrides); } async deleteSchedule(id: string, version: number): Promise { await this.del(`/health/doctor-schedules/${id}`, { version }); } async createAppointment(overrides: AppointmentData): Promise>> { return this.post('/health/appointments', overrides); } async updateAppointmentStatus(id: string, version: number, status: string): Promise>> { return this.put(`/health/appointments/${id}/status`, { status, version }); } async deleteAppointment(id: string, version: number): Promise { await this.del(`/health/appointments/${id}`, { version }); } async createFollowUpTemplate(overrides?: Partial): Promise>> { return this.post('/health/follow-up-templates', overrides ?? {}); } async deleteFollowUpTemplate(id: string, version: number): Promise { await this.del(`/health/follow-up-templates/${id}`, { version }); } async createFollowUpTask(overrides: FollowUpTaskData): Promise>> { return this.post('/health/follow-up-tasks', overrides); } async deleteFollowUpTask(id: string, version: number): Promise { await this.del(`/health/follow-up-tasks/${id}`, { version }); } async createAlertRule(overrides?: Partial): Promise>> { return this.post('/health/alert-rules', overrides ?? {}); } async deleteAlertRule(id: string, version: number): Promise { await this.del(`/health/alert-rules/${id}`, { version }); } async listAlerts(): Promise>[]> { const res = await this.get<{ data: VEntity>[] }>('/health/alerts'); return res.data ?? []; } async acknowledgeAlert(id: string, version: number): Promise>> { return this.put(`/health/alerts/${id}/acknowledge`, { version }); } async resolveAlert(id: string, version: number): Promise>> { return this.put(`/health/alerts/${id}/resolve`, { version }); } async dismissAlert(id: string, version: number): Promise>> { return this.put(`/health/alerts/${id}/dismiss`, { version }); } private async headers(): Promise> { return { 'Content-Type': 'application/json', ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), }; } private async parseJson(res: Response, method: string, path: string): Promise { if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(`${method} ${path} → HTTP ${res.status}: ${text.slice(0, 200)}`); } const json = await res.json(); if (!json.success) throw new Error(`${method} ${path} failed: ${json.error ?? 'unknown'}`); return json.data as T; } private async get(path: string): Promise { const res = await fetch(`${API_BASE}${path}`, { headers: await this.headers() }); return this.parseJson(res, 'GET', path); } private async post(path: string, body: unknown): Promise { const res = await fetch(`${API_BASE}${path}`, { method: 'POST', headers: await this.headers(), body: JSON.stringify(body), }); return this.parseJson(res, 'POST', path); } private async put(path: string, body: unknown): Promise { const res = await fetch(`${API_BASE}${path}`, { method: 'PUT', headers: await this.headers(), body: JSON.stringify(body), }); return this.parseJson(res, 'PUT', path); } private async del(path: string, body?: unknown): Promise { const res = await fetch(`${API_BASE}${path}`, { method: 'DELETE', headers: await this.headers(), body: body ? JSON.stringify(body) : undefined, }); if (res.status === 204) return; if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(`DELETE ${path} → HTTP ${res.status}: ${text.slice(0, 200)}`); } const json = await res.json(); if (!json.success) throw new Error(`DELETE ${path} failed: ${json.error ?? 'unknown'}`); } private async rawPost(path: string, body: unknown): Promise { const res = await fetch(`${API_BASE}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(`POST ${path} → HTTP ${res.status}: ${text.slice(0, 200)}`); } const json = await res.json(); if (!json.success) throw new Error(`POST ${path} failed: ${json.error ?? 'unknown'}`); return json as T; } }