/** * Minimal, dependency-free HTTP helpers for talking to the backend directly * (bypassing the browser) during E2E setup and ground-truth assertions. * * Deliberately hits the backend at 127.0.0.1:8000, not through the Vite * proxy: these calls run in plain Node (global-setup) or alongside a * Playwright test (spec files), never inside a browser page, so there is no * CORS concern and no need for the proxy or the httpOnly refresh cookie — * just a Bearer access token obtained via `apiLogin`. */ export const API_BASE = 'http://127.0.0.1:8000' // Seeded by `backend/scripts/seed_demo.py` — see that file's module // docstring. Not a secret: dev-only, fixed, printed by the script itself. export const DEMO_EMAIL = 'demo@example.com' export const DEMO_PASSWORD = 'DemoPass123!' export async function waitForBackendHealth(timeoutMs = 60_000): Promise { const healthUrl = `${API_BASE}/health` const deadline = Date.now() + timeoutMs let lastError: unknown = null while (Date.now() < deadline) { try { const res = await fetch(healthUrl) if (res.ok) return } catch (err) { lastError = err } await new Promise((resolve) => setTimeout(resolve, 500)) } throw new Error(`Backend never reported healthy at ${healthUrl}: ${String(lastError)}`) } async function parseJsonOrThrow(res: Response, what: string): Promise { if (!res.ok) { throw new Error(`${what} failed: ${res.status} ${await res.text()}`) } return (await res.json()) as T } interface LoginResponse { access_token: string } export async function apiLogin(email: string, password: string): Promise { const res = await fetch(`${API_BASE}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }) const data = await parseJsonOrThrow(res, 'login') return data.access_token } export interface ApiChild { id: string name: string birth_date: string } export interface ApiUserContext { personal_data_uuid: string workspace_id: string parent_name: string children: ApiChild[] } export async function apiGetContext(token: string): Promise { const res = await fetch(`${API_BASE}/api/auth/context`, { headers: { Authorization: `Bearer ${token}` }, }) return parseJsonOrThrow(res, 'get user context') } export type PlannedDayType = 'sjukpenning' | 'lagsta' | 'vacation' export interface PlannedDayPayload { workspace_id: string child_id: string parent_personal_data_uuid: string date: string day_type: PlannedDayType is_dubbeldag: boolean } export async function apiCreatePlannedDay(token: string, payload: PlannedDayPayload): Promise { const res = await fetch(`${API_BASE}/api/planned-days`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify(payload), }) if (!res.ok) { throw new Error(`create planned day ${payload.date} failed: ${res.status} ${await res.text()}`) } } export interface ApiAge4Projection { personal_data_uuid: string savable_after_b4: number expiring_at_b4: number projected_left_at_b4: number savable_cap_at_b4: number } export interface ApiWorkspaceOverview { parent_projections: ApiAge4Projection[] combined_expiring_at_b4: number combined_days_left: number } export async function apiGetOverview( token: string, workspaceId: string, childId: string, ): Promise { const url = `${API_BASE}/api/analytics/${workspaceId}/overview?child_id=${childId}` const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } }) return parseJsonOrThrow(res, 'get workspace overview') }