/** * 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"); });