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", "/v1/email-change"]) { 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/free (deprecated)", () => { expect(spec.paths["/v1/signup/free"]).toBeDefined(); expect(spec.paths["/v1/signup/free"].post).toBeDefined(); expect(spec.paths["/v1/signup/free"].post.deprecated).toBe(true); }); it("excludes GET /v1/billing/success (browser redirect, not public API)", () => { expect(spec.paths["/v1/billing/success"]).toBeUndefined(); }); it("excludes POST /v1/billing/webhook (internal Stripe endpoint)", () => { expect(spec.paths["/v1/billing/webhook"]).toBeUndefined(); }); }); 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"'); }); }); describe("BUG-097: Footer Support link in partial", () => { it("shared footer partial contains Support mailto 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('href="mailto:support@docfast.dev"'); expect(footer).toContain(">Support"); }); }); describe("BUG-095: docs.html footer has all links", () => { it("docs footer contains all expected links", async () => { const fs = await import("fs"); const path = await import("path"); const docs = fs.readFileSync( path.join(__dirname, "../../public/docs.html"), "utf-8" ); const expectedLinks = [ { href: "/", text: "Home" }, { href: "/docs", text: "Docs" }, { href: "/examples", text: "Examples" }, { href: "/status", text: "API Status" }, { href: "mailto:support@docfast.dev", text: "Support" }, { href: "/#change-email", text: "Change Email" }, { href: "/impressum", text: "Impressum" }, { href: "/privacy", text: "Privacy Policy" }, { href: "/terms", text: "Terms of Service" }, ]; for (const link of expectedLinks) { expect(docs).toContain(`href="${link.href}"`); expect(docs).toContain(`${link.text}`); } expect(docs).toContain('class="open-email-change"'); }); }); });