diff --git a/frontend/src/api/client.test.ts b/frontend/src/api/client.test.ts new file mode 100644 index 00000000..bf9bcfbb --- /dev/null +++ b/frontend/src/api/client.test.ts @@ -0,0 +1,27 @@ +import { http, HttpResponse } from "msw"; +import { server } from "../test/msw"; +import { apiFetch } from "./client"; + +test("body-less POST omits content-type (avoids Fastify empty-body 400)", async () => { + let contentType: string | null = "unset"; + server.use( + http.post("http://localhost:8787/sessions/s1/resume", ({ request }) => { + contentType = request.headers.get("content-type"); + return HttpResponse.json({ id: "s1" }); + }), + ); + await apiFetch("/sessions/s1/resume", { method: "POST" }); + expect(contentType).toBeNull(); +}); + +test("POST with a body sends application/json content-type", async () => { + let contentType: string | null = null; + server.use( + http.post("http://localhost:8787/sessions/s1/steer", ({ request }) => { + contentType = request.headers.get("content-type"); + return new HttpResponse(null, { status: 204 }); + }), + ); + await apiFetch("/sessions/s1/steer", { method: "POST", body: JSON.stringify({ text: "hi" }) }); + expect(contentType).toContain("application/json"); +}); diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 04fe194e..58d8c175 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,10 +1,16 @@ const BASE = import.meta.env.VITE_BACKEND_URL ?? "http://localhost:8787"; export async function apiFetch(path: string, init?: RequestInit): Promise { - const res = await fetch(`${BASE}${path}`, { - headers: { "content-type": "application/json" }, - ...init, - }); + // Only declare a JSON content-type when we actually send a body. Body-less + // POSTs (resume, close) would otherwise make Fastify reject the empty body + // with FST_ERR_CTP_EMPTY_JSON_BODY (400). + const headers: Record = { + ...(init?.headers as Record | undefined), + }; + if (init?.body != null && !("content-type" in headers) && !("Content-Type" in headers)) { + headers["content-type"] = "application/json"; + } + const res = await fetch(`${BASE}${path}`, { ...init, headers }); if (!res.ok) throw new Error(`${res.status} ${await res.text().catch(() => "")}`); return res.status === 204 ? (undefined as T) : ((await res.json()) as T); }