import type { Principal, PrincipalId } from "@suaveplan/api-types/principal"; import type { TenantId } from "@suaveplan/api-types/tenant"; import { afterAll, beforeAll, describe, expect, it, } from "@suaveplan/testing/runner"; import { type BunWsEngine, type BunWsSocketSink, createBunWsEngine, } from "../../src/engine/bun-ws-engine.js"; const tenant = "tenant-int" as unknown as TenantId; const principal: Principal = { id: "p-int" as unknown as PrincipalId, scopes: [], claims: {}, }; interface BunServeShim { upgrade(req: Request, ctx?: { data?: Record }): boolean; publish?: unknown; stop(graceful?: boolean): void; hostname: string; port: number; } // Bun is available when this test is run under Bun. Vitest also runs under // Bun in this monorepo. Skip if Bun.serve is missing (e.g. pure Node). const bunGlobal = ( globalThis as unknown as { Bun?: { serve(opts: unknown): BunServeShim; }; } ).Bun; const shouldRun = bunGlobal !== undefined; describe.runIf(shouldRun)("end-to-end Bun.serve WS", () => { let server: BunServeShim | null = null; let engine: BunWsEngine; let port = 0; beforeAll(async () => { engine = createBunWsEngine({ reconnectTokenSecret: new Uint8Array(32).fill(7), heartbeatMs: 5_000, maxQueuedMessages: 32, }); // Build a Bun.serve wiring that adapts every accepted ws into a // BunWsSocketSink and calls engine.attach on upgrade. server = bunGlobal!.serve({ port: 0, fetch( req: Request, srv: { upgrade( req: Request, ctx?: { data?: Record } ): boolean; } ): Response | undefined { const url = new URL(req.url); if (url.pathname === "/ws") { const id = url.searchParams.get("id") ?? "anon"; const ok = srv.upgrade(req, { data: { id } }); return ok ? undefined : new Response("upgrade failed", { status: 400 }); } return new Response("ok"); }, websocket: { open(ws: { data: { id: string }; send(data: string): void; close(code: number, reason?: string): void; ping?(): void; }): void { const sink: BunWsSocketSink = { send: (data: string) => ws.send(data), close: (code: number, reason?: string) => ws.close(code, reason), ping: () => ws.ping?.(), }; const conn = engine.attach({ id: ws.data.id, principal, tenantId: tenant, capabilities: new Set(), tokenExpiresAt: Number.MAX_SAFE_INTEGER, sink, }); conn.subscribe("orders"); }, message(): void { /* unused for this smoke */ }, close(): void { /* unused for this smoke */ }, }, }); port = server.port; }); afterAll(async () => { server?.stop(true); await engine.dispose(); }); it("handshake → subscribe → broadcast → receive over a real socket", { timeout: 30_000, }, async () => { const url = `ws://${server!.hostname}:${port}/ws?id=client-1`; const received: string[] = []; const ws = new WebSocket(url); await new Promise((resolve, reject) => { ws.onopen = () => resolve(); ws.onerror = (err) => reject(err); }); ws.onmessage = (ev) => { received.push(typeof ev.data === "string" ? ev.data : ""); }; // Wait one microtask so engine.attach + subscribe have run. await new Promise((r) => setTimeout(r, 50)); engine.broadcast({ tenant, capabilities: new Set() }, "orders", { tenant, payload: { hello: "world" }, }); await new Promise((r) => setTimeout(r, 100)); expect(received.length).toBeGreaterThan(0); const parsed = JSON.parse(received[0] as string); expect(parsed.topic).toBe("orders"); ws.close(); }); });