mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-23 18:31:20 +00:00
* fix-nodes-unicode-browser-node-names * fix(nodes): preserve compact browser node selectors * fix(nodes): normalize unicode node names canonically * fix(nodes): scope compact browser selectors * fix(nodes): preserve meaningful unicode marks * chore: leave changelog updates to release automation --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
// Node utility tests cover node selection defaults and gateway fallback between
|
|
// current and legacy node list methods.
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const gatewayMocks = vi.hoisted(() => ({
|
|
callGatewayTool: vi.fn(),
|
|
}));
|
|
vi.mock("./gateway.js", () => ({
|
|
callGatewayTool: (...args: unknown[]) => gatewayMocks.callGatewayTool(...args),
|
|
}));
|
|
|
|
import type { NodeListNode } from "./nodes-utils.js";
|
|
import { listNodes, resolveNodeIdFromList } from "./nodes-utils.js";
|
|
|
|
function node({ nodeId, ...overrides }: Partial<NodeListNode> & { nodeId: string }): NodeListNode {
|
|
return {
|
|
nodeId,
|
|
caps: ["canvas"],
|
|
connected: true,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
gatewayMocks.callGatewayTool.mockReset();
|
|
});
|
|
|
|
describe("resolveNodeIdFromList defaults", () => {
|
|
it("keeps compact display-name matching opt-in", () => {
|
|
const nodes = [node({ nodeId: "mac-1", displayName: "Mac Studio" })];
|
|
|
|
expect(() => resolveNodeIdFromList(nodes, "MacStudio")).toThrow(/unknown node: MacStudio/);
|
|
expect(
|
|
resolveNodeIdFromList(nodes, "MacStudio", false, { allowCompactDisplayName: true }),
|
|
).toBe("mac-1");
|
|
});
|
|
|
|
it("falls back to most recently connected node when multiple non-Mac candidates exist", () => {
|
|
const nodes: NodeListNode[] = [
|
|
node({ nodeId: "ios-1", platform: "ios", connectedAtMs: 1 }),
|
|
node({ nodeId: "android-1", platform: "android", connectedAtMs: 2 }),
|
|
];
|
|
|
|
expect(resolveNodeIdFromList(nodes, undefined, true)).toBe("android-1");
|
|
});
|
|
|
|
it("preserves local Mac preference when exactly one local Mac candidate exists", () => {
|
|
const nodes: NodeListNode[] = [
|
|
node({ nodeId: "ios-1", platform: "ios" }),
|
|
node({ nodeId: "mac-1", platform: "macos" }),
|
|
];
|
|
|
|
expect(resolveNodeIdFromList(nodes, undefined, true)).toBe("mac-1");
|
|
});
|
|
|
|
it("uses stable nodeId ordering when connectedAtMs is unavailable", () => {
|
|
// Deterministic tie-breaking keeps repeated tool calls from bouncing
|
|
// between connected nodes.
|
|
const nodes: NodeListNode[] = [
|
|
node({ nodeId: "z-node", platform: "ios", connectedAtMs: undefined }),
|
|
node({ nodeId: "a-node", platform: "android", connectedAtMs: undefined }),
|
|
];
|
|
|
|
expect(resolveNodeIdFromList(nodes, undefined, true)).toBe("a-node");
|
|
});
|
|
});
|
|
|
|
describe("listNodes", () => {
|
|
it("falls back to node.pair.list only when node.list is unavailable", async () => {
|
|
// Old gateways only expose node.pair.list; newer authorization failures
|
|
// must still surface instead of being hidden by fallback.
|
|
gatewayMocks.callGatewayTool
|
|
.mockRejectedValueOnce(new Error("unknown method: node.list"))
|
|
.mockResolvedValueOnce({
|
|
pending: [],
|
|
paired: [{ nodeId: "pair-1", displayName: "Pair 1", platform: "ios", remoteIp: "1.2.3.4" }],
|
|
});
|
|
|
|
const signal = new AbortController().signal;
|
|
await expect(listNodes({}, signal)).resolves.toEqual([
|
|
{
|
|
nodeId: "pair-1",
|
|
displayName: "Pair 1",
|
|
platform: "ios",
|
|
remoteIp: "1.2.3.4",
|
|
},
|
|
]);
|
|
expect(gatewayMocks.callGatewayTool).toHaveBeenNthCalledWith(
|
|
1,
|
|
"node.list",
|
|
{},
|
|
{},
|
|
{ signal },
|
|
);
|
|
expect(gatewayMocks.callGatewayTool).toHaveBeenNthCalledWith(
|
|
2,
|
|
"node.pair.list",
|
|
{},
|
|
{},
|
|
{
|
|
signal,
|
|
},
|
|
);
|
|
});
|
|
|
|
it("rethrows unexpected node.list failures without fallback", async () => {
|
|
gatewayMocks.callGatewayTool.mockRejectedValueOnce(
|
|
new Error("gateway closed (1008): unauthorized"),
|
|
);
|
|
|
|
const signal = new AbortController().signal;
|
|
await expect(listNodes({}, signal)).rejects.toThrow("gateway closed (1008): unauthorized");
|
|
expect(gatewayMocks.callGatewayTool).toHaveBeenCalledTimes(1);
|
|
expect(gatewayMocks.callGatewayTool).toHaveBeenCalledWith("node.list", {}, {}, { signal });
|
|
});
|
|
});
|