Files
padhle/backend/test/inputHygiene.test.js
T
2026-09-15 04:08:55 -04:00

72 lines
2.3 KiB
JavaScript

/**
* Robustness/input-hygiene: non-string message bodies must not crash the
* signed-in OR anonymous chat route or leak stack traces. The validator
* previously called text.trim() without a typeof check, so `{"text":123}` etc.
* threw synchronously outside the try/catch → an unhandled 500.
*/
import test, { before, after } from "node:test";
import assert from "node:assert/strict";
import express from "express";
import { createChatRouter } from "../src/routes/chat.js";
import { optionalAuth } from "../src/middleware/supabaseAuth.js";
let app;
let server;
let base;
const streamCalls = [];
async function fakeStream() {
streamCalls.push(1);
return "ok";
}
before(async () => {
app = express();
app.use(express.json());
app.use("/api/chat", optionalAuth, createChatRouter({ streamFn: fakeStream }));
server = app.listen(0, "127.0.0.1");
await new Promise((r) => server.once("listening", r));
base = `http://127.0.0.1:${server.address().port}`;
});
after(async () => {
if (server) await new Promise((r) => server.close(r));
});
const badTexts = [
123,
["hello"],
{},
true,
{ toString: () => "x" },
[],
];
test("non-string `text` values are rejected cleanly (400), never a 500 crash", async () => {
for (const bad of badTexts) {
const res = await fetch(`${base}/api/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: bad }),
});
const body = await res.text();
assert.equal(res.status, 400, `text=${JSON.stringify(bad)} -> 400, got ${res.status} ${body}`);
assert.ok(!body.includes("at "), "no stack-trace leak in the response");
}
assert.equal(streamCalls.length, 0, "no AI call was made for any rejected body");
});
test("non-string grade/subject are rejected cleanly, valid text still works", async () => {
const badMeta = await fetch(`${base}/api/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "hello", grade: 5, subject: ["math"] }),
});
assert.equal(badMeta.status, 400, "invalid grade/subject types -> 400");
const ok = await fetch(`${base}/api/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "valid question" }),
});
assert.equal(ok.status, 200, "valid text reaches the stream");
});