import { mkdirSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; import { expect, test } from "@playwright/test"; import { createServer, type ViteDevServer } from "vite"; import { createApp } from "../../apps/api/src/app.js"; import { EventHub } from "../../apps/api/src/event-hub.js"; const eventHub = new EventHub(); let app: Awaited>; let vite: ViteDevServer; let webUrl: string; test.beforeAll(async () => { app = await createApp({ browserGate: false, eventHub, networkBoundary: { allowTestPort: true }, }); app.get("/__test/entity", async (request) => ({ entity_ref: (request.query as { ref?: string }).ref ?? null })); app.get("/__test/models", async () => ({ source: "rest" })); const apiUrl = await app.listen({ host: "127.0.0.1", port: 0 }); vite = await createServer({ configFile: false, root: process.cwd(), server: { host: "127.0.0.1", port: 0, proxy: { "/__test": apiUrl, "/api": apiUrl, }, }, }); await vite.listen(); const address = vite.httpServer?.address(); if (!address || typeof address === "string") throw new Error("Vite did not expose a test port."); webUrl = `http://127.0.0.1:${address.port}`; }); test.afterAll(async () => { eventHub.disconnectAll(); await vite.close(); await app.close(); }); test("SSE remains a hint and recovers through REST", async ({ page }) => { const published = [ { entity_ref: "projects:project-1", event_id: 40, event_type: "project_state_changed", occurred_at: "2026-07-27T09:00:00.000Z", state_version: 2, }, { config_set_version: 9, entity_ref: "models:current", event_id: 42, event_type: "model_config_changed", occurred_at: "2026-07-27T09:00:01.000Z", }, ] as const; await page.goto(`${webUrl}/tests/e2e/fixtures/event-sync.html`); await expect(page.locator("#status")).toHaveText("connected"); expect(eventHub.subscriberCount).toBe(1); eventHub.publish(published[0]); await expect .poll(() => page.evaluate(() => window.wp0EventFixture.timeline.filter((entry) => entry.action === "refetch_entity").length)) .toBe(1); eventHub.publish(published[1]); await expect .poll(() => page.evaluate(() => window.wp0EventFixture.timeline.filter((entry) => entry.action === "bootstrap").length)) .toBe(1); await expect .poll(() => page.evaluate(() => window.wp0EventFixture.timeline.filter((entry) => entry.action === "refetch_models").length)) .toBe(1); eventHub.disconnectAll(); await expect .poll(() => page.evaluate(() => window.wp0EventFixture.timeline.filter((entry) => entry.action === "bootstrap").length)) .toBe(2); const timeline = await page.evaluate(() => window.wp0EventFixture.timeline); const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_EVT; if (evidenceDirectory) { mkdirSync(evidenceDirectory, { recursive: true }); writeFileSync( resolve(evidenceDirectory, "sse-events.json"), `${JSON.stringify({ events: published, status: "passed" }, null, 2)}\n`, ); writeFileSync( resolve(evidenceDirectory, "network-timeline.json"), `${JSON.stringify({ status: "passed", timeline }, null, 2)}\n`, ); } });