padhle - post migration
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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");
|
||||
});
|
||||
Reference in New Issue
Block a user