Initial Commit
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* §16 conformance proof — the worker-proxy package consumes the
|
||||
* `@suaveplan/api-worker-rpc` contract directly (client + server +
|
||||
* MessagePort transport). This test exercises the canonical
|
||||
* `runWorkerRpcConformance` harness with a minimal handler set to
|
||||
* prove the consumer is wire-compatible end-to-end (request frame,
|
||||
* response frame, abort propagation) using the same port transport
|
||||
* the package builds on top of in `src/main-side` / `src/worker-side`.
|
||||
*/
|
||||
import { afterEach, describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { runWorkerRpcConformance } from "../../../api-worker-rpc/src/testing/conformance.js";
|
||||
|
||||
const handles: Array<{ dispose(): void }> = [];
|
||||
afterEach(() => {
|
||||
while (handles.length > 0) handles.pop()?.dispose();
|
||||
});
|
||||
|
||||
describe("api-worker-rpc contract conformance (consumer-side)", () => {
|
||||
it("round-trips a request through the canonical harness", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const h = runWorkerRpcConformance(() => ({
|
||||
echo: (params: ReadonlyArray<unknown>) => params[0],
|
||||
}));
|
||||
handles.push(h);
|
||||
const result = await h.client.call<number>("echo", [42]);
|
||||
expect(result).toBe(42);
|
||||
});
|
||||
|
||||
it("surfaces UNKNOWN_METHOD from the server", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const h = runWorkerRpcConformance(() => ({}));
|
||||
handles.push(h);
|
||||
await expect(h.client.call("missing", [])).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import { afterEach, describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { createProxyPair, waitFor } from "../../src/testing/testing.js";
|
||||
|
||||
type Map = { ping: number; user: { id: string } };
|
||||
|
||||
const teardowns: Array<() => Promise<void>> = [];
|
||||
afterEach(async () => {
|
||||
while (teardowns.length > 0) {
|
||||
const t = teardowns.pop();
|
||||
if (t !== undefined) await t();
|
||||
}
|
||||
});
|
||||
|
||||
function fresh() {
|
||||
const pair = createProxyPair<Map>();
|
||||
teardowns.push(pair.tearDown);
|
||||
return pair;
|
||||
}
|
||||
|
||||
async function flushSubscribe(): Promise<void> {
|
||||
// Allow the streaming subscribe RPC to fully open before emitting,
|
||||
// so chunks arrive in FIFO order.
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
|
||||
describe("events-bus conformance via worker-proxy surface", () => {
|
||||
it("FIFO ordering preserved across the port", {
|
||||
timeout: 10000,
|
||||
}, async () => {
|
||||
const { bus, client } = fresh();
|
||||
const seen: number[] = [];
|
||||
client.on("ping", (n) => {
|
||||
seen.push(n);
|
||||
});
|
||||
await flushSubscribe();
|
||||
for (let i = 0; i < 25; i++) bus.emit("ping", i);
|
||||
await waitFor(() => seen.length === 25, 3000);
|
||||
expect(seen).toStrictEqual(Array.from({ length: 25 }, (_, i) => i));
|
||||
});
|
||||
|
||||
it("multiple listeners each receive the same event", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const { bus, client } = fresh();
|
||||
const a: number[] = [];
|
||||
const b: number[] = [];
|
||||
client.on("ping", (n) => {
|
||||
a.push(n);
|
||||
});
|
||||
client.on("ping", (n) => {
|
||||
b.push(n);
|
||||
});
|
||||
await flushSubscribe();
|
||||
bus.emit("ping", 42);
|
||||
await waitFor(() => a.length === 1 && b.length === 1, 2000);
|
||||
expect(a).toStrictEqual([42]);
|
||||
expect(b).toStrictEqual([42]);
|
||||
});
|
||||
|
||||
it("typed payloads survive the round-trip", { timeout: 5000 }, async () => {
|
||||
const { bus, client } = fresh();
|
||||
const seen: Array<{ id: string }> = [];
|
||||
client.on("user", (u) => {
|
||||
seen.push(u);
|
||||
});
|
||||
await flushSubscribe();
|
||||
bus.emit("user", { id: "u_1" });
|
||||
bus.emit("user", { id: "u_2" });
|
||||
await waitFor(() => seen.length === 2, 2000);
|
||||
expect(seen).toStrictEqual([{ id: "u_1" }, { id: "u_2" }]);
|
||||
});
|
||||
|
||||
it("worker → main emitAsync round-trips dispatch metadata", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const { bus, client } = fresh();
|
||||
let aSeen = 0;
|
||||
let bSeen = 0;
|
||||
bus.on("ping", () => {
|
||||
aSeen++;
|
||||
});
|
||||
bus.on("ping", () => {
|
||||
bSeen++;
|
||||
});
|
||||
const r = await client.emitAsync("ping", 1);
|
||||
expect(r.delivered).toBe(2);
|
||||
expect(r.errorCount).toBe(0);
|
||||
expect(aSeen).toBe(1);
|
||||
expect(bSeen).toBe(1);
|
||||
});
|
||||
|
||||
it("idempotent listeners (re-add same fn) deliver once per add", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const { bus, client } = fresh();
|
||||
const seen: number[] = [];
|
||||
const fn = (n: number): void => {
|
||||
seen.push(n);
|
||||
};
|
||||
client.on("ping", fn);
|
||||
client.on("ping", fn);
|
||||
await flushSubscribe();
|
||||
bus.emit("ping", 1);
|
||||
await waitFor(() => seen.length === 2, 2000);
|
||||
expect(seen).toStrictEqual([1, 1]);
|
||||
});
|
||||
|
||||
it("interceptor running main-side observes worker-issued emit", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const { bus, client } = fresh();
|
||||
let interceptors = 0;
|
||||
bus.use(async (_env, next) => {
|
||||
interceptors++;
|
||||
return next();
|
||||
});
|
||||
await client.emitAsync("ping", 1);
|
||||
expect(interceptors).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user