import { APIRequestContext, request } from "@playwright/test" import { ADMIN_TOKEN, API_BASE } from "../../fixtures/test-constants" let adminContext: APIRequestContext | null = null type CollectionEntry = { id?: string _id?: string | { $oid?: string } [key: string]: unknown } type CollectionQueryOptions = { filter?: Record sort?: string limit?: number offset?: number projection?: string lookup?: string } function getEntryId(entry: CollectionEntry): string | undefined { if (typeof entry.id === "string") return entry.id if (typeof entry._id === "string") return entry._id if (entry._id && typeof entry._id === "object" && typeof entry._id.$oid === "string") return entry._id.$oid return undefined } function buildCollectionUrl(collection: string, options: CollectionQueryOptions = {}, id?: string): string { const searchParams = new URLSearchParams() if (options.filter) searchParams.set("filter", JSON.stringify(options.filter)) if (options.sort) searchParams.set("sort", options.sort) if (typeof options.limit === "number") searchParams.set("limit", String(options.limit)) if (typeof options.offset === "number") searchParams.set("offset", String(options.offset)) if (options.projection) searchParams.set("projection", options.projection) if (options.lookup) searchParams.set("lookup", options.lookup) const path = `${API_BASE}/${collection}${id ? `/${id}` : ""}` const query = searchParams.toString() return query ? `${path}?${query}` : path } async function parseJsonResponse( res: Awaited>, contextLabel: string ): Promise { const contentType = res.headers()["content-type"] || "" if (contentType.includes("text/html")) { throw new Error(`${contextLabel} returned HTML instead of JSON. Check CODING_URL or API proxy setup.`) } return (await res.json()) as T } /** * Get or create a singleton admin API context with the ADMIN_TOKEN. */ export async function getAdminContext(baseURL: string): Promise { if (ADMIN_TOKEN === "CHANGE_ME") { throw new Error("ADMIN_TOKEN is not configured for Playwright seed and cleanup") } if (!adminContext) { adminContext = await request.newContext({ baseURL, ignoreHTTPSErrors: true, extraHTTPHeaders: { Token: ADMIN_TOKEN, }, }) } return adminContext } export async function listCollectionEntries( baseURL: string, collection: string, options: CollectionQueryOptions = {} ): Promise { const ctx = await getAdminContext(baseURL) const res = await ctx.get(buildCollectionUrl(collection, options)) if (!res.ok()) { throw new Error(`Failed to list ${collection}: ${res.status()} ${res.statusText()}`) } const body = await parseJsonResponse(res, `Listing ${collection}`) if (Array.isArray(body)) return body as T[] if (body && typeof body === "object" && "data" in body && Array.isArray(body.data)) return body.data as T[] return [] } export async function createCollectionEntry( baseURL: string, collection: string, entry: Record ): Promise { const ctx = await getAdminContext(baseURL) const res = await ctx.post(`${API_BASE}/${collection}`, { data: entry }) if (!res.ok()) { throw new Error(`Failed to create ${collection}: ${res.status()} ${res.statusText()}`) } return await parseJsonResponse(res, `Creating ${collection}`) } export async function updateCollectionEntry( baseURL: string, collection: string, id: string, entry: Record ): Promise { const ctx = await getAdminContext(baseURL) const res = await ctx.put(`${API_BASE}/${collection}/${id}`, { data: entry }) if (!res.ok()) { throw new Error(`Failed to update ${collection}/${id}: ${res.status()} ${res.statusText()}`) } return await parseJsonResponse(res, `Updating ${collection}/${id}`) } export async function deleteCollectionEntry(baseURL: string, collection: string, id: string): Promise { const ctx = await getAdminContext(baseURL) const res = await ctx.delete(`${API_BASE}/${collection}/${id}`) return res.ok() } export async function findFirstEntry( baseURL: string, collection: string, options: CollectionQueryOptions = {} ): Promise { const entries = await listCollectionEntries(baseURL, collection, { ...options, limit: 1 }) return entries[0] || null } export async function upsertCollectionEntry( baseURL: string, collection: string, filter: Record, entry: Record ): Promise { const existing = await findFirstEntry(baseURL, collection, { filter }) const existingId = existing ? getEntryId(existing) : undefined if (existingId) { return updateCollectionEntry(baseURL, collection, existingId, entry) } return createCollectionEntry(baseURL, collection, entry) } export async function cleanupAllTestData(baseURL: string): Promise<{ users: number }> { void baseURL return { users: 0 } } /** * Dispose the admin API context. Call in globalTeardown. */ export async function disposeAdminApi(): Promise { if (adminContext) { await adminContext.dispose() adminContext = null } }