mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 06:47:28 +00:00
fix(commandboard-api): return 400 for malformed JSON bodies instead of 500
Malformed JSON on any POST endpoint previously bubbled a SyntaxError to
the top-level handler, which mapped every throw to 500. Client input
errors now respond 400 { "error": "Invalid JSON body" } via a typed
InvalidJsonBodyError thrown from readJson().
Adds contract tests covering all four POST endpoints plus a control
asserting schema-invalid (but well-formed) bodies still return 422.
Fixes #11
This commit is contained in:
parent
850cf5ea44
commit
d29808aea2
2 changed files with 47 additions and 1 deletions
|
|
@ -182,3 +182,34 @@ describe("CommandBoard API contracts", () => {
|
||||||
expect(Array.isArray(body.errors)).toBe(true);
|
expect(Array.isArray(body.errors)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("invalid JSON request bodies", () => {
|
||||||
|
const postEndpoints = [
|
||||||
|
"/api/tasks",
|
||||||
|
"/api/plugins/sh1pt/actions/publish",
|
||||||
|
"/api/plugins/c0mpute/jobs/dispatch",
|
||||||
|
"/api/plugins/c0mpute/quotes"
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(postEndpoints)("returns 400 (not 500) for malformed JSON on %s", async (endpoint) => {
|
||||||
|
const response = await fetch(`${baseUrl}${endpoint}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: "{invalid json"
|
||||||
|
});
|
||||||
|
const body = await response.json() as { error: string };
|
||||||
|
|
||||||
|
expect(response.status).toBe(400);
|
||||||
|
expect(body).toEqual({ error: "Invalid JSON body" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("still returns 422 for well-formed JSON that fails schema validation", async () => {
|
||||||
|
const response = await fetch(`${baseUrl}/api/tasks`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ title: "missing required fields" })
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(422);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,10 @@ export function createCommandBoardServer() {
|
||||||
try {
|
try {
|
||||||
await route(request, response);
|
await route(request, response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof InvalidJsonBodyError) {
|
||||||
|
json(response, 400, { error: error.message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
json(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
json(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -281,13 +285,24 @@ function text(response: ServerResponse, status: number, contentType: string, bod
|
||||||
response.end(body);
|
response.end(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InvalidJsonBodyError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("Invalid JSON body");
|
||||||
|
this.name = "InvalidJsonBodyError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function readJson(request: IncomingMessage) {
|
async function readJson(request: IncomingMessage) {
|
||||||
const chunks: Buffer[] = [];
|
const chunks: Buffer[] = [];
|
||||||
for await (const chunk of request) {
|
for await (const chunk of request) {
|
||||||
chunks.push(Buffer.from(chunk));
|
chunks.push(Buffer.from(chunk));
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.parse(Buffer.concat(chunks).toString("utf8")) as unknown;
|
try {
|
||||||
|
return JSON.parse(Buffer.concat(chunks).toString("utf8")) as unknown;
|
||||||
|
} catch {
|
||||||
|
throw new InvalidJsonBodyError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue