import { expect, test } from "@playwright/test"; /** * Stage-7 closure smoke — `api-ws-bun` tenant-scoped broadcast. * * Two clients connect to `/ws` on different tenants (`tenant-a` / * `tenant-b`) and subscribe to the SAME topic (`updates`). A publish * scoped to `tenant-a` MUST reach only the tenant-a client; the * tenant-b client MUST NOT see it. This proves the * `SubscriptionBus.broadcast` tenant key — design §11.4 — is honoured * by the engine's `broadcast(...)` helper. */ interface WsConnection { readonly ws: WebSocket; readonly received: unknown[]; close(): void; } async function openWs( baseURL: string, tenant: string, topic: string ): Promise { const wsUrl = baseURL.replace(/^http/, "ws"); const ws = new WebSocket( `${wsUrl}/ws?tenant=${encodeURIComponent(tenant)}&topic=${encodeURIComponent(topic)}` ); const received: unknown[] = []; ws.addEventListener("message", (ev) => { try { received.push(JSON.parse(String(ev.data))); } catch { received.push(ev.data); } }); await new Promise((resolve, reject) => { const timer = setTimeout( () => reject(new Error("ws open timeout")), 5000 ); ws.addEventListener( "open", () => { clearTimeout(timer); resolve(); }, { once: true } ); ws.addEventListener( "error", (e) => { clearTimeout(timer); reject(new Error(`ws error: ${String(e)}`)); }, { once: true } ); }); return { ws, received, close() { try { ws.close(); } catch { /* idempotent */ } }, }; } async function settle(ms = 200): Promise { await new Promise((resolve) => setTimeout(resolve, ms)); } test.describe("foundation-stack-engines — ws-bun tenant isolation", () => { test("3. tenant-A broadcast reaches only tenant-A subscriber, never tenant-B", async ({ request, baseURL, }) => { expect(baseURL).toBeDefined(); const clientA = await openWs(baseURL as string, "tenant-a", "updates"); const clientB = await openWs(baseURL as string, "tenant-b", "updates"); try { // Let the engine.attach + subscribe calls settle. await settle(150); const publish = await request.post("/ws/publish", { data: { topic: "updates", tenant: "tenant-a", payload: { hello: "from-tenant-a" }, }, }); expect(publish.status()).toBe(200); const pBody = (await publish.json()) as { delivered: number }; // Exactly one subscriber (the tenant-a client) should match. expect(pBody.delivered).toBe(1); // Allow the send pipeline to flush over the socket. await settle(300); expect(clientA.received.length).toBe(1); const env = clientA.received[0] as { type?: string; topic?: string; payload?: Record; }; expect(env.type).toBe("broadcast"); expect(env.topic).toBe("updates"); expect(env.payload).toEqual({ hello: "from-tenant-a" }); // Tenant-B subscriber must NOT have received the tenant-A // broadcast — proves cross-tenant isolation. expect(clientB.received.length).toBe(0); } finally { clientA.close(); clientB.close(); } }); });