From 150b45db01df782be7ad0843e7504138c67883bd Mon Sep 17 00:00:00 2001 From: RobertGuajardo Date: Mon, 9 Mar 2026 15:37:23 +0100 Subject: [PATCH 1/2] chore: add .env, *.key, *.pem and other security entries to .gitignore --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index 99cce13a..fd95db21 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,12 @@ dist/ *.sqlite3 progress.txt progress*.txt +.env +.env.local +.env.*.local +*.key +*.pem +*.secret +coverage/ +.nyc_output/ +*.log From 2fdba10978fda3d491765382e69619b3efdca842 Mon Sep 17 00:00:00 2001 From: RobertGuajardo Date: Mon, 9 Mar 2026 16:34:08 +0100 Subject: [PATCH 2/2] feat: US-001 - Add POST /echo endpoint to dashboard server --- src/server/dashboard.ts | 20 +++++++++ tests/echo-endpoint.test.ts | 86 +++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 tests/echo-endpoint.test.ts diff --git a/src/server/dashboard.ts b/src/server/dashboard.ts index 59c1533e..33c48df4 100644 --- a/src/server/dashboard.ts +++ b/src/server/dashboard.ts @@ -137,6 +137,26 @@ export function startDashboard(port = 3333): http.Server { } } + // POST /echo - round-trip endpoint for testing + if (req.method === "POST" && p === "/echo") { + const chunks: Buffer[] = []; + req.on("data", (chunk: Buffer) => chunks.push(chunk)); + req.on("end", () => { + const body = Buffer.concat(chunks).toString("utf-8"); + let parsed: unknown; + try { + parsed = JSON.parse(body); + } catch { + res.writeHead(400, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }); + res.end(JSON.stringify({ error: "invalid JSON" })); + return; + } + res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }); + res.end(JSON.stringify(parsed)); + }); + return; + } + // Serve frontend serveHTML(res); }); diff --git a/tests/echo-endpoint.test.ts b/tests/echo-endpoint.test.ts new file mode 100644 index 00000000..4bbd32b1 --- /dev/null +++ b/tests/echo-endpoint.test.ts @@ -0,0 +1,86 @@ +/** + * Tests for the POST /echo endpoint on the dashboard server. + * Verifies JSON round-trip, error handling, and method filtering. + */ +import { describe, it, after } from "node:test"; +import assert from "node:assert/strict"; +import http from "node:http"; +import { startDashboard } from "../dist/server/dashboard.js"; + +function postJson(port: number, path: string, body: string): Promise<{ status: number; body: string }> { + return new Promise((resolve, reject) => { + const req = http.request( + { hostname: "127.0.0.1", port, path, method: "POST", headers: { "Content-Type": "application/json" } }, + (res) => { + const chunks: Buffer[] = []; + res.on("data", (chunk: Buffer) => chunks.push(chunk)); + res.on("end", () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf-8") })); + } + ); + req.on("error", reject); + req.write(body); + req.end(); + }); +} + +function getRequest(port: number, path: string): Promise<{ status: number; body: string }> { + return new Promise((resolve, reject) => { + const req = http.request( + { hostname: "127.0.0.1", port, path, method: "GET" }, + (res) => { + const chunks: Buffer[] = []; + res.on("data", (chunk: Buffer) => chunks.push(chunk)); + res.on("end", () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf-8") })); + } + ); + req.on("error", reject); + req.end(); + }); +} + +describe("POST /echo endpoint", () => { + it("returns HTTP 200 and echoes a simple JSON body", async () => { + const server = startDashboard(0); + await new Promise((resolve) => server.once("listening", resolve)); + const port = (server.address() as { port: number }).port; + after(() => server.close()); + + const res = await postJson(port, "/echo", JSON.stringify({ hello: "world" })); + assert.equal(res.status, 200); + assert.deepEqual(JSON.parse(res.body), { hello: "world" }); + }); + + it("returns HTTP 200 and echoes a nested JSON body", async () => { + const server = startDashboard(0); + await new Promise((resolve) => server.once("listening", resolve)); + const port = (server.address() as { port: number }).port; + after(() => server.close()); + + const res = await postJson(port, "/echo", JSON.stringify({ nested: { a: 1 } })); + assert.equal(res.status, 200); + assert.deepEqual(JSON.parse(res.body), { nested: { a: 1 } }); + }); + + it("returns HTTP 400 with error message for invalid JSON body", async () => { + const server = startDashboard(0); + await new Promise((resolve) => server.once("listening", resolve)); + const port = (server.address() as { port: number }).port; + after(() => server.close()); + + const res = await postJson(port, "/echo", "not valid json"); + assert.equal(res.status, 400); + assert.deepEqual(JSON.parse(res.body), { error: "invalid JSON" }); + }); + + it("GET /echo falls through to serveHTML (not handled by echo route)", async () => { + const server = startDashboard(0); + await new Promise((resolve) => server.once("listening", resolve)); + const port = (server.address() as { port: number }).port; + after(() => server.close()); + + const res = await getRequest(port, "/echo"); + // Should serve HTML, not JSON echo + assert.equal(res.status, 200); + assert.ok(res.body.includes("