Initial Commit
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
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<string, unknown> }): 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<unknown>;
|
||||
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<string, unknown> }
|
||||
): 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<void>((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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user