import { describe, it, expect, beforeAll } from "vitest"; import request from "supertest"; import { app } from "../index.js"; describe("App-level routes", () => { describe("POST /v1/signup/* (410 Gone)", () => { it("returns 410 for POST /v1/signup", async () => { const res = await request(app).post("/v1/signup"); expect(res.status).toBe(410); expect(res.body.error).toContain("discontinued"); expect(res.body.demo_endpoint).toBe("/v1/demo/html"); expect(res.body.pro_url).toBe("https://docfast.dev/#pricing"); }); it("returns 410 for POST /v1/signup/free", async () => { const res = await request(app).post("/v1/signup/free"); expect(res.status).toBe(410); expect(res.body.error).toContain("discontinued"); }); it("returns 410 for GET /v1/signup", async () => { const res = await request(app).get("/v1/signup"); expect(res.status).toBe(410); expect(res.body.demo_endpoint).toBeDefined(); }); }); describe("GET /api", () => { it("returns API discovery info", async () => { const res = await request(app).get("/api"); expect(res.status).toBe(200); expect(res.body.name).toBe("DocFast API"); expect(res.body.version).toBeDefined(); expect(Array.isArray(res.body.endpoints)).toBe(true); expect(res.body.endpoints.length).toBeGreaterThan(0); }); }); describe("404 handler", () => { it("returns JSON 404 for API paths (/v1/*)", async () => { const res = await request(app).get("/v1/nonexistent"); expect(res.status).toBe(404); expect(res.body.error).toContain("Not Found"); }); it("returns JSON 404 for API paths (/api/*)", async () => { const res = await request(app).get("/api/nonexistent"); expect(res.status).toBe(404); expect(res.body.error).toContain("Not Found"); }); it("returns HTML 404 for browser paths", async () => { const res = await request(app).get("/nonexistent-page"); expect(res.status).toBe(404); expect(res.headers["content-type"]).toContain("text/html"); expect(res.text).toContain("404"); }); }); describe("CORS behavior", () => { it("returns restricted origin for auth routes", async () => { for (const path of ["/v1/signup", "/v1/recover", "/v1/billing", "/v1/demo"]) { const res = await request(app).get(path); expect(res.headers["access-control-allow-origin"]).toBe("https://docfast.dev"); } }); it("returns wildcard origin for other routes", async () => { for (const path of ["/v1/convert", "/health"]) { const res = await request(app).get(path); expect(res.headers["access-control-allow-origin"]).toBe("*"); } }); it("returns 204 for OPTIONS preflight", async () => { const res = await request(app).options("/v1/signup"); expect(res.status).toBe(204); expect(res.headers["access-control-allow-methods"]).toContain("GET"); expect(res.headers["access-control-allow-headers"]).toContain("X-API-Key"); }); }); describe("Request ID", () => { it("adds X-Request-Id header to responses", async () => { const res = await request(app).get("/api"); expect(res.headers["x-request-id"]).toBeDefined(); }); it("echoes provided X-Request-Id", async () => { const res = await request(app).get("/api").set("X-Request-Id", "test-id-123"); expect(res.headers["x-request-id"]).toBe("test-id-123"); }); }); describe("OpenAPI spec completeness", () => { let spec: any; beforeAll(async () => { const res = await request(app).get("/openapi.json"); expect(res.status).toBe(200); spec = res.body; }); it("includes POST /v1/signup/verify", () => { expect(spec.paths["/v1/signup/verify"]).toBeDefined(); expect(spec.paths["/v1/signup/verify"].post).toBeDefined(); }); it("includes GET /v1/billing/success", () => { expect(spec.paths["/v1/billing/success"]).toBeDefined(); expect(spec.paths["/v1/billing/success"].get).toBeDefined(); }); it("includes POST /v1/billing/webhook", () => { expect(spec.paths["/v1/billing/webhook"]).toBeDefined(); expect(spec.paths["/v1/billing/webhook"].post).toBeDefined(); }); }); describe("Security headers", () => { it("includes helmet security headers", async () => { const res = await request(app).get("/api"); expect(res.headers["x-content-type-options"]).toBe("nosniff"); expect(res.headers["x-frame-options"]).toBeDefined(); }); it("includes permissions-policy header", async () => { const res = await request(app).get("/api"); expect(res.headers["permissions-policy"]).toContain("camera=()"); }); }); describe("BUG-092: Footer Change Email link", () => { it("landing page footer contains Change Email link", async () => { const res = await request(app).get("/"); expect(res.status).toBe(200); const html = res.text; expect(html).toContain('class="open-email-change"'); expect(html).toMatch(/footer-links[\s\S]*open-email-change[\s\S]*Change Email/); }); it("sub-page footer partial contains Change Email link", async () => { const fs = await import("fs"); const path = await import("path"); const footer = fs.readFileSync( path.join(__dirname, "../../public/partials/_footer.html"), "utf-8" ); expect(footer).toContain('class="open-email-change"'); expect(footer).toContain('href="/#change-email"'); }); }); });