// IMP-56 (#90) u20 — vitest coverage for the pure request builders exported // by `BottomActions`. The React component itself is not rendered (jsdom / // @testing-library NOT in Front devDependencies — verified against the prior // u14 `imp90_structure_overlay.test.tsx` pattern); we test the deterministic // pieces that drive the network payload sent to the u18 / u19 middlewares. // // Upstream / downstream contracts (verified by prior units): // - u18 /api/connect : body shape = { run_id, slug } (Front/vite.config.ts // handleConnectMirror — `imp90_connect_endpoint.test.ts`). // - u19 /api/export : body shape = { run_id }; response = raw text/html // with `Content-Disposition: attachment; filename=".html"` // (Front/vite.config.ts handleExportStandalone — // `imp90_export_endpoint.test.ts`). // // u20 scope: builders only. Any drift in URL or JSON shape fails here before // the request leaves the client. Toast / fetch / blob plumbing is not tested // (it would require jsdom + a fetch mock; the existing server-side tests // already pin the wire contract). import { describe, it, expect } from "vitest"; import { buildConnectRequest, buildExportRequest, buildDownloadFilename, } from "../src/components/BottomActions"; describe("buildConnectRequest", () => { it("targets /api/connect", () => { const { url } = buildConnectRequest("run_42", "mdx_03"); expect(url).toBe("/api/connect"); }); it("emits { run_id, slug } JSON body — matches u18 middleware shape", () => { const { body } = buildConnectRequest("run_42", "mdx_03"); expect(JSON.parse(body)).toEqual({ run_id: "run_42", slug: "mdx_03" }); }); it("preserves zero-length and unicode run_id verbatim (server validates)", () => { const { body } = buildConnectRequest("", "x"); expect(JSON.parse(body)).toEqual({ run_id: "", slug: "x" }); const { body: uni } = buildConnectRequest("런", "슬러그"); expect(JSON.parse(uni)).toEqual({ run_id: "런", slug: "슬러그" }); }); it("does not leak extra keys (frame swap / overrides etc.)", () => { const { body } = buildConnectRequest("r", "s"); expect(Object.keys(JSON.parse(body)).sort()).toEqual(["run_id", "slug"]); }); }); describe("buildExportRequest", () => { it("targets /api/export", () => { const { url } = buildExportRequest("run_42"); expect(url).toBe("/api/export"); }); it("emits { run_id } JSON body — matches u19 middleware shape", () => { const { body } = buildExportRequest("run_42"); expect(JSON.parse(body)).toEqual({ run_id: "run_42" }); }); it("does not leak extra keys (slug / format etc.)", () => { const { body } = buildExportRequest("r"); expect(Object.keys(JSON.parse(body))).toEqual(["run_id"]); }); it("preserves zero-length and unicode run_id verbatim (server validates)", () => { expect(JSON.parse(buildExportRequest("").body)).toEqual({ run_id: "" }); expect(JSON.parse(buildExportRequest("런").body)).toEqual({ run_id: "런" }); }); }); describe("buildDownloadFilename", () => { it("returns .html for the a[download] click chain", () => { expect(buildDownloadFilename("run_42")).toBe("run_42.html"); }); it("appends exactly one .html suffix even when run_id already ends in .html", () => { // The server-side `Content-Disposition` already carries the same // filename; we mirror it verbatim so browser default behavior wins. // We intentionally do NOT strip a trailing `.html` — run_id is the // backend's `Path(args.mdx_path).stem`-style key, which never contains // a dot suffix (validated by `isValidUserOverridesKey` at u18/u19). expect(buildDownloadFilename("foo.html")).toBe("foo.html.html"); }); it("returns just .html for empty run_id (server rejects upstream)", () => { expect(buildDownloadFilename("")).toBe(".html"); }); });