Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ dist/
*.sqlite3
progress.txt
progress*.txt
.env
.env.local
.env.*.local
*.key
*.pem
*.secret
coverage/
.nyc_output/
*.log
20 changes: 20 additions & 0 deletions src/server/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
86 changes: 86 additions & 0 deletions tests/echo-endpoint.test.ts
Original file line number Diff line number Diff line change
@@ -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<void>((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<void>((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<void>((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<void>((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("<!"), `expected HTML response, got: ${res.body.slice(0, 100)}`);
});
});