test(frontend): Playwright e2e F1 vs backend+fake-pi (hermetic)
Adds a real-browser Playwright e2e of the full F1 disambiguation loop, driven against the real backend + fake binaries (no VPN, no real Pi, no Python). - frontend/e2e/fixtures/fake-tht.mjs: stubs tht CLI (session new/list/show) - frontend/e2e/fixtures/fake-pi.mjs: wraps harness fake-pi-rpc with f1_disambiguation.json - frontend/playwright.config.ts: two webServer entries (backend:8799, frontend:5199) - frontend/e2e/f1.spec.ts: open app → create session → wait for SelectWidget → respond Bug fixes discovered during e2e: - useSessionStream: add addEventListener for named SSE events (backend sends 'event: ui_request' etc.; onmessage only fires for unnamed 'event: message') - FakeEventSource: add no-op addEventListener/removeEventListener stubs - backend SSE route: add CORS headers manually in writeHead() since reply.raw bypasses the @fastify/cors onSend hook - backend: install and register @fastify/cors for all non-SSE routes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Generated
+37
@@ -6,6 +6,7 @@
|
||||
"": {
|
||||
"name": "thothii-backend",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^11.2.0",
|
||||
"fastify": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -478,6 +479,26 @@
|
||||
"fast-uri": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/cors": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-11.2.0.tgz",
|
||||
"integrity": "sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fastify-plugin": "^5.0.0",
|
||||
"toad-cache": "^3.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/error": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz",
|
||||
@@ -1389,6 +1410,22 @@
|
||||
"toad-cache": "^3.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fastify-plugin": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz",
|
||||
"integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
||||
|
||||
+10
-2
@@ -8,6 +8,14 @@
|
||||
"test": "vitest run",
|
||||
"start": "node dist/server.js"
|
||||
},
|
||||
"dependencies": { "fastify": "^5.0.0" },
|
||||
"devDependencies": { "typescript": "^5.6.0", "tsx": "^4.19.0", "vitest": "^2.1.0", "@types/node": "^22.0.0" }
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^11.2.0",
|
||||
"fastify": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vitest": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Fastify, { type FastifyInstance } from "fastify";
|
||||
import cors from "@fastify/cors";
|
||||
import type { AppConfig } from "./config.js";
|
||||
import { ThtRunner } from "./tht/tht-runner.js";
|
||||
import { PiProcessManager } from "./pi/pi-process-manager.js";
|
||||
@@ -17,6 +18,9 @@ export interface BuildAppDeps {
|
||||
export function buildApp(config: AppConfig, deps?: BuildAppDeps): FastifyInstance {
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
// Allow any origin in dev/e2e; tighten in production via config if needed.
|
||||
app.register(cors, { origin: true, credentials: true });
|
||||
|
||||
const tht = deps?.thtRunner ?? new ThtRunner({
|
||||
thtBin: config.thtBin,
|
||||
harnessDir: config.harnessDir,
|
||||
|
||||
@@ -44,7 +44,16 @@ export function sessionRoutes(app: FastifyInstance, d: { mgr: PiProcessManager;
|
||||
app.get("/sessions/:id/events", (req, reply) => {
|
||||
const id = (req.params as any).id;
|
||||
const rt = d.mgr.get(id);
|
||||
reply.raw.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
|
||||
// Add CORS headers manually: reply.raw.writeHead bypasses Fastify's onSend hooks
|
||||
// (where @fastify/cors injects headers), so we must set them explicitly here.
|
||||
const origin = (req.headers.origin as string | undefined) ?? "*";
|
||||
reply.raw.writeHead(200, {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
"Access-Control-Allow-Origin": origin,
|
||||
"Access-Control-Allow-Credentials": "true",
|
||||
});
|
||||
const send = (event: string, data: object) => reply.raw.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
||||
const off = d.hub.subscribe(id, send, rt?.bridge.pendingWidget() ?? null);
|
||||
req.raw.on("close", off);
|
||||
|
||||
Reference in New Issue
Block a user