import { existsSync, readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; const apiTestEnv = readApiTestEnv(); const API_BASE_URL = (process.env.API_BASE_URL ?? apiTestEnv.API_BASE_URL)?.replace(/\/+$/, ""); const API_TOKEN = process.env.API_TOKEN ?? apiTestEnv.API_TOKEN; const API_TEST_SESSION_COOKIE = process.env.API_TEST_SESSION_COOKIE ?? apiTestEnv.API_TEST_SESSION_COOKIE; const API_TEST_ADMIN_SESSION_COOKIE = process.env.API_TEST_ADMIN_SESSION_COOKIE ?? apiTestEnv.API_TEST_ADMIN_SESSION_COOKIE; const API_TEST_USER_ID = process.env.API_TEST_USER_ID ?? apiTestEnv.API_TEST_USER_ID; const API_TEST_ORGANIZATION_ID = process.env.API_TEST_ORGANIZATION_ID ?? apiTestEnv.API_TEST_ORGANIZATION_ID; const API_TEST_API_KEY_ID = process.env.API_TEST_API_KEY_ID ?? apiTestEnv.API_TEST_API_KEY_ID; const ALLOW_AUTH_WRITE_TESTS = process.env.ALLOW_AUTH_WRITE_TESTS === "true" || apiTestEnv.ALLOW_AUTH_WRITE_TESTS === "true"; const RUN_ID = process.env.RUN_ID ?? apiTestEnv.RUN_ID ?? `api-test-${Date.now()}`; const liveDescribe = API_BASE_URL ? describe : describe.skip; const writeDescribe = API_BASE_URL && ALLOW_AUTH_WRITE_TESTS ? describe : describe.skip; const fixtureDescribe = API_BASE_URL && API_TEST_SESSION_COOKIE && API_TEST_ADMIN_SESSION_COOKIE ? describe : describe.skip; interface OpenAPIDocument { openapi: string; info: { title: string; }; servers?: Array<{ url: string }>; paths: Record>; } type JsonObject = Record; async function requestJson( path: string, options: RequestInit = {}, ): Promise<{ status: number; headers: Headers; body: T }> { if (!API_BASE_URL) { throw new Error("Set API_BASE_URL before running live API tests."); } const headers = new Headers(options.headers); headers.set("accept", "application/json"); if (options.body && !headers.has("content-type")) { headers.set("content-type", "application/json"); } if (API_TOKEN) { headers.set("authorization", `Bearer ${API_TOKEN}`); } const response = await fetch(new URL(`${API_BASE_URL}${path}`), { ...options, headers, }); const text = await response.text(); const body = text ? (JSON.parse(text) as T) : (undefined as T); return { status: response.status, headers: response.headers, body }; } function readApiTestEnv(): Record { const path = process.env.API_TEST_ENV_FILE ?? ".api-test.env"; if (!existsSync(path)) return {}; const values: Record = {}; for (const line of readFileSync(path, "utf8").split(/\r?\n/)) { if (!line || line.trimStart().startsWith("#")) continue; const index = line.indexOf("="); if (index === -1) continue; values[line.slice(0, index).trim()] = line .slice(index + 1) .trim() .replace(/^"|"$/g, "") .replace(/\\"/g, '"') .replace(/\\\\/g, "\\"); } return values; } function expectJsonResponse(headers: Headers): void { expect(headers.get("content-type") ?? "").toContain("application/json"); } liveDescribe("cfw-auth live API contract smoke tests", () => { it("serves the Better Auth OpenAPI document", async () => { const response = await requestJson("/open-api/generate-schema"); expect(response.status).toBe(200); expectJsonResponse(response.headers); expect(response.body).toMatchObject({ openapi: expect.stringMatching(/^3\./), info: { title: "Better Auth", }, }); expect(response.body.paths).toEqual( expect.objectContaining({ "/get-session": expect.any(Object), "/ok": expect.any(Object), "/sign-in/email": expect.any(Object), "/sign-up/email": expect.any(Object), }), ); expect(Object.keys(response.body.paths).length).toBeGreaterThan(20); }); it("reports API readiness from the documented ok endpoint", async () => { const response = await requestJson<{ ok: boolean }>("/ok"); expect(response.status).toBe(200); expectJsonResponse(response.headers); expect(response.body).toEqual({ ok: true }); }); it("returns an explicit anonymous session state without credentials", async () => { const response = await requestJson("/get-session"); expect(response.status).toBe(200); expectJsonResponse(response.headers); expect(response.body).toBeNull(); }); it("rejects invalid email sign-in without creating account data", async () => { const response = await requestJson("/sign-in/email", { method: "POST", body: JSON.stringify({ email: `missing-${RUN_ID}@example.invalid`, password: "not-the-right-password", }), }); expect([400, 401, 403]).toContain(response.status); expectJsonResponse(response.headers); expect(response.body).toEqual( expect.objectContaining({ message: expect.any(String), }), ); }); }); writeDescribe("cfw-auth live API write scenarios", () => { it("can sign up a namespaced email user when write tests are explicitly enabled", async () => { const email = `${RUN_ID}@example.invalid`; const response = await requestJson<{ token?: string | null; user?: JsonObject }>( "/sign-up/email", { method: "POST", body: JSON.stringify({ name: `API Test ${RUN_ID}`, email, password: "TestPassword123!", rememberMe: false, }), }, ); expect(response.status).toBe(200); expectJsonResponse(response.headers); expect(response.body.user).toEqual( expect.objectContaining({ id: expect.any(String), email, name: `API Test ${RUN_ID}`, emailVerified: expect.any(Boolean), }), ); }); }); fixtureDescribe("cfw-auth live API fixture-backed scenarios", () => { it("reads the bootstrapped user session", async () => { const response = await requestJson<{ session: JsonObject; user: JsonObject }>("/get-session", { headers: { cookie: API_TEST_SESSION_COOKIE, }, }); expect(response.status).toBe(200); expect(response.body.user).toEqual( expect.objectContaining({ id: API_TEST_USER_ID, emailVerified: true, }), ); }); it("lets the bootstrapped admin read the test user", async () => { const response = await requestJson( `/admin/get-user?id=${encodeURIComponent(API_TEST_USER_ID ?? "")}`, { headers: { cookie: API_TEST_ADMIN_SESSION_COOKIE, }, }, ); expect(response.status).toBe(200); expect(response.body).toEqual( expect.objectContaining({ id: API_TEST_USER_ID, }), ); }); it("lists the bootstrapped organization and API key for the test user", async () => { const organizations = await requestJson("/organization/list", { headers: { cookie: API_TEST_SESSION_COOKIE, }, }); expect(organizations.status).toBe(200); expect(organizations.body).toEqual( expect.arrayContaining([ expect.objectContaining({ id: API_TEST_ORGANIZATION_ID, }), ]), ); const apiKeys = await requestJson<{ apiKeys: JsonObject[] }>("/api-key/list", { headers: { cookie: API_TEST_SESSION_COOKIE, }, }); expect(apiKeys.status).toBe(200); expect(apiKeys.body.apiKeys).toEqual( expect.arrayContaining([ expect.objectContaining({ id: API_TEST_API_KEY_ID, }), ]), ); }); });