/**
 * Machine surfaces must fail as JSON, not HTML.
 *
 * The global app.notFound / app.onError handlers render the shared HTML
 * error pages. Before this was gated, an unmatched /api/* path returned a
 * full HTML document with `content-type: text/html`, so REST, MCP and OCI
 * registry clients hit a JSON.parse error instead of a readable envelope.
 * The production-readiness gate (scripts/production-readiness.mjs, "api-json")
 * checks exactly this against the live site.
 */

import { describe, expect, it } from "bun:test";
import app from "../app";

async function get(path: string, headers: Record<string, string> = {}) {
  return app.fetch(new Request(`http://localhost${path}`, { headers }));
}

describe("unmatched machine-surface paths return JSON", () => {
  // `/v2/*` (OCI registry) and `/npm/*` are deliberately absent: their own
  // routers claim the whole subtree and emit their own spec-shaped JSON
  // errors, so the app-level fallback never fires for them. They stay in
  // JSON_ROOTS purely as a safety net.
  const jsonPaths = [
    "/api/definitely-not-real",
    "/api/v2/definitely-not-real",
    "/mcp/definitely-not-real",
    "/graphql/definitely-not-real",
    "/scim/definitely-not-real",
    "/.well-known/definitely-not-real",
  ];

  for (const path of jsonPaths) {
    it(`${path} → 404 application/json`, async () => {
      const res = await get(path);
      expect(res.status).toBe(404);
      expect(res.headers.get("content-type")).toContain("application/json");
      const body = await res.json();
      expect(body.error).toBe("Not found");
      expect(body.path).toBe(path);
    });
  }

  it("a deep unmatched API path is JSON too", async () => {
    const res = await get("/api/v2/repos/nope/nope/nope/nope");
    expect(res.status).toBe(404);
    expect(res.headers.get("content-type")).toContain("application/json");
  });
});

describe("HTML surfaces are unaffected", () => {
  it("a path that merely starts with an API root's letters stays HTML", async () => {
    // "/mcpanything" must NOT be captured by the "/mcp" machine surface —
    // it is a one-segment path and belongs to the /:owner profile route.
    const res = await get("/mcpanything", { accept: "text/html" });
    expect(res.headers.get("content-type")).toContain("text/html");
  });

  it("a normal repo path still renders HTML", async () => {
    const res = await get("/someowner/somerepo", { accept: "text/html" });
    expect(res.headers.get("content-type")).toContain("text/html");
  });
});
