File size: 2,954 Bytes
fc93158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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", {}, {});
  });
});