Files
openclaw/src/agents/tools/nodes-utils.test.ts
2026-02-26 21:26:54 +00:00

86 lines
2.9 KiB
TypeScript

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("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", () => {
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 () => {
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" }],
});
await expect(listNodes({})).resolves.toEqual([
{
nodeId: "pair-1",
displayName: "Pair 1",
platform: "ios",
remoteIp: "1.2.3.4",
},
]);
expect(gatewayMocks.callGatewayTool).toHaveBeenNthCalledWith(1, "node.list", {}, {});
expect(gatewayMocks.callGatewayTool).toHaveBeenNthCalledWith(2, "node.pair.list", {}, {});
});
it("rethrows unexpected node.list failures without fallback", async () => {
gatewayMocks.callGatewayTool.mockRejectedValueOnce(
new Error("gateway closed (1008): unauthorized"),
);
await expect(listNodes({})).rejects.toThrow("gateway closed (1008): unauthorized");
expect(gatewayMocks.callGatewayTool).toHaveBeenCalledTimes(1);
expect(gatewayMocks.callGatewayTool).toHaveBeenCalledWith("node.list", {}, {});
});
});