Files
gstack/design/test/serve.test.ts
T
garrytan 5ec930d569 test: replace product tests that fake the product with real-boundary tests (F)
- design: serve.test.ts drove an inline mirror server; now two tests run the
  real serve() on an ephemeral port (reload confinement, submit exit 0).
- setup-gbrain: rollback + voyage tests execute the template-extracted init
  blocks (3 sites) instead of drifted local bash copies.
- terminal-agent: internalHandler source greps replaced by a behavioral
  /internal/grant + /internal/revoke auth matrix (no/wrong/valid token).
- /health: server-security-surface and the server-auth / security-audit-r2 /
  sidebar-tabs source greps fold into one liveness-only check on the real
  body; the L4 sidecar wiring gets a behavioral /pty-inject-scan test.
- delete tautologies (browser-manager onDisconnect, memory-command #12),
  ios swiftui tap fixture self-check, memory-ingest put_page grep, detach
  source greps, sidebar-agent absence pins, dead-CSS pins + the dead CSS,
  security-audit-r2 Task 1 + the test-only meta-commands re-export,
  duplicate generated-SKILL.md checks.
- make-pdf coverage-gaps cases move into their owner test files.
2026-09-29 04:43:28 +00:00

123 lines
4.3 KiB
TypeScript

/**
* Legacy single-process board server (`$D compare --serve --no-daemon`).
*
* Runs the real `serve()` from design/src/serve.ts in a child process on an
* ephemeral port (port 0), because serve() never returns and exits the
* process on submit. The daemon owns the default path (daemon.test.ts); this
* file proves the escape hatch still serves, confines /api/reload to the
* board directory, and exits 0 after writing feedback.json on submit.
*/
import { afterAll, describe, expect, test } from "bun:test";
import fs from "fs";
import os from "os";
import path from "path";
const SERVE_MODULE = path.resolve(import.meta.dir, "../src/serve.ts");
interface RunningServe {
proc: ReturnType<typeof Bun.spawn>;
base: string;
dir: string;
html: string;
}
const running: RunningServe[] = [];
async function startServe(): Promise<RunningServe> {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "design-serve-"));
const html = path.join(dir, "board.html");
fs.writeFileSync(html, "<html><body>BOARD_V1</body></html>");
const binDir = path.join(dir, "bin");
fs.mkdirSync(binDir);
for (const opener of ["xdg-open", "open"]) {
fs.writeFileSync(path.join(binDir, opener), "#!/bin/sh\nexit 0\n", { mode: 0o755 });
}
const proc = Bun.spawn(
[process.execPath, "-e", `import { serve } from ${JSON.stringify(SERVE_MODULE)}; await serve({ html: ${JSON.stringify(html)}, port: 0, timeout: 60 });`],
{
env: { ...process.env, PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ""}` },
stdout: "pipe",
stderr: "pipe",
},
);
const reader = proc.stderr.getReader();
const decoder = new TextDecoder();
let seen = "";
const deadline = Date.now() + 15_000;
while (Date.now() < deadline) {
const { value, done } = await reader.read();
if (done) break;
seen += decoder.decode(value);
const match = /SERVE_STARTED: port=(\d+)/.exec(seen);
if (match) {
reader.releaseLock();
const handle = { proc, base: `http://127.0.0.1:${match[1]}`, dir, html };
running.push(handle);
return handle;
}
}
proc.kill();
throw new Error(`serve() never reported SERVE_STARTED:\n${seen}`);
}
afterAll(() => {
for (const { proc, dir } of running) {
proc.kill();
fs.rmSync(dir, { recursive: true, force: true });
}
});
describe("design serve() (legacy --no-daemon path)", () => {
test("serves the board, confines /api/reload to the board dir, and exits 0 on submit", async () => {
const s = await startServe();
const page = await fetch(`${s.base}/`);
expect(page.status).toBe(200);
expect(await page.text()).toContain("BOARD_V1");
expect(await (await fetch(`${s.base}/api/progress`)).json()).toEqual({ status: "serving" });
const outside = path.join(os.tmpdir(), `design-serve-outside-${process.pid}.html`);
fs.writeFileSync(outside, "SECRET");
try {
const escape = await fetch(`${s.base}/api/reload`, {
method: "POST",
body: JSON.stringify({ html: outside }),
});
expect(escape.status).toBe(403);
} finally {
fs.rmSync(outside, { force: true });
}
const dirReload = await fetch(`${s.base}/api/reload`, {
method: "POST",
body: JSON.stringify({ html: s.dir }),
});
expect(dirReload.status).toBe(403);
const v2 = path.join(s.dir, "board-v2.html");
fs.writeFileSync(v2, "<html><body>BOARD_V2</body></html>");
const reload = await fetch(`${s.base}/api/reload`, { method: "POST", body: JSON.stringify({ html: v2 }) });
expect(await reload.json()).toEqual({ reloaded: true });
expect(await (await fetch(`${s.base}/`)).text()).toContain("BOARD_V2");
const submit = await fetch(`${s.base}/api/feedback`, {
method: "POST",
body: JSON.stringify({ regenerated: false, preferred: "A" }),
});
expect(await submit.json()).toEqual({ received: true, action: "submitted" });
expect(await s.proc.exited).toBe(0);
expect(JSON.parse(fs.readFileSync(path.join(s.dir, "feedback.json"), "utf-8"))).toEqual({
regenerated: false,
preferred: "A",
});
});
test("a second server in the same process binds its own ephemeral port", async () => {
const a = await startServe();
const b = await startServe();
expect(a.base).not.toBe(b.base);
expect((await fetch(`${a.base}/`)).status).toBe(200);
expect((await fetch(`${b.base}/`)).status).toBe(200);
});
});