mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-04 13:21:40 +00:00
* fix(gateway): clear wake state on node removal * fix(gateway): invalidate wake flows on node removal * fix(gateway): preserve active wakes on disconnect * fix(gateway): share node removal runtime cleanup * fix(gateway): clean up pending node work reliably * fix(gateway): bind node work to pairing generations * fix(gateway): fence stale node pairing sessions * fix(gateway): tighten node pairing ownership * fix(gateway): close node pairing mutation races * fix(gateway): bind node sessions to pairing generation * fix(gateway): harden node pairing generation guards * fix(gateway): close node pairing generation races * fix(gateway): guard APNs transport ownership * fix(gateway): revoke reapproved node transports * fix(gateway): guard retired node lifecycles * fix(gateway): preserve pending work across reconnects * fix(gateway): bind node subscriptions to pairing generations * fix(gateway): settle subscription fanout failures * fix(gateway): fence pairing generation ownership * fix(gateway): fence asynchronous node events * fix(gateway): fence watch results and voice wake fanout * fix(gateway): guard voice wake generation fanout * fix(gateway): carry authenticated snapshot generation * fix(gateway): bind node state to pairing generation * fix(gateway): align generation guards with current base * fix(gateway): enforce pairing-owned node lifecycle * fix(gateway): fence detached voice session writes * chore(deps): bump proxyline to 0.3.4 * fix(gateway): close pairing lifecycle races Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com> * fix(gateway): distinguish pending action mutation Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com> * test(gateway): align lifecycle coverage Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com> * test(skills): split remote reconciliation coverage Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com> * style: format pairing lifecycle changes Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
75 lines
2.6 KiB
TypeScript
75 lines
2.6 KiB
TypeScript
import { describe, expect, test, vi } from "vitest";
|
|
import type { SerializedEventPayload } from "./node-registry.js";
|
|
import { createNodeSubscriptionManager } from "./server-node-subscriptions.js";
|
|
|
|
describe("node subscription manager", () => {
|
|
test("routes events with each subscribed node pairing generation", async () => {
|
|
const manager = createNodeSubscriptionManager();
|
|
const sent: Array<{
|
|
nodeId: string;
|
|
pairingGeneration: string;
|
|
event: string;
|
|
payloadJSON?: SerializedEventPayload | null;
|
|
}> = [];
|
|
|
|
manager.subscribe("node-a", "generation-a", "main");
|
|
manager.subscribe("node-b", "generation-b", "main");
|
|
await manager.sendToSession("main", "chat", { ok: true }, (event) => {
|
|
sent.push(event);
|
|
});
|
|
|
|
expect(sent).toHaveLength(2);
|
|
expect(sent.map((event) => event.nodeId).toSorted()).toEqual(["node-a", "node-b"]);
|
|
expect(sent.map((event) => event.pairingGeneration).toSorted()).toEqual([
|
|
"generation-a",
|
|
"generation-b",
|
|
]);
|
|
});
|
|
|
|
test("unsubscribeAll clears both subscription indexes", async () => {
|
|
const manager = createNodeSubscriptionManager();
|
|
const sent: string[] = [];
|
|
const sendEvent = (event: { nodeId: string; event: string }) => {
|
|
sent.push(`${event.nodeId}:${event.event}`);
|
|
};
|
|
|
|
manager.subscribe("node-a", "generation-a", "main");
|
|
manager.subscribe("node-a", "generation-a", "secondary");
|
|
manager.unsubscribeAll("node-a");
|
|
await manager.sendToSession("main", "tick", {}, sendEvent);
|
|
await manager.sendToSession("secondary", "tick", {}, sendEvent);
|
|
|
|
expect(sent).toStrictEqual([]);
|
|
});
|
|
|
|
test("settles sender failures without rejecting fire-and-forget fanout", async () => {
|
|
const manager = createNodeSubscriptionManager();
|
|
const sent: string[] = [];
|
|
|
|
manager.subscribe("node-a", "generation-a", "main");
|
|
manager.subscribe("node-b", "generation-b", "main");
|
|
await expect(
|
|
manager.sendToSession("main", "tick", {}, ({ nodeId }) => {
|
|
if (nodeId === "node-a") {
|
|
throw new Error("transport failed");
|
|
}
|
|
sent.push(nodeId);
|
|
}),
|
|
).resolves.toBeUndefined();
|
|
|
|
expect(sent).toStrictEqual(["node-b"]);
|
|
});
|
|
|
|
test("drops unserializable payloads without rejecting fanout", async () => {
|
|
const manager = createNodeSubscriptionManager();
|
|
const sendEvent = vi.fn();
|
|
|
|
manager.subscribe("node-a", "generation-a", "main");
|
|
await expect(
|
|
manager.sendToSession("main", "tick", { invalid: 1n }, sendEvent),
|
|
).resolves.toBeUndefined();
|
|
|
|
expect(sendEvent).not.toHaveBeenCalled();
|
|
});
|
|
});
|