File size: 2,648 Bytes
f778c12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { describe, expect, it } from "vitest";
import { encodePairingSetupCode } from "../../pairing/setup-code.js";
import { resolveNodeGatewayOptions, resolveNodePairGatewayOptions } from "./gateway-options.js";

const TLS_FINGERPRINT = "ab".repeat(32);

describe("node gateway options", () => {
  it("preserves ordered pairing endpoint candidates and pins only the direct endpoint", () => {
    const pair = resolveNodePairGatewayOptions(
      encodePairingSetupCode({
        url: "wss://192.168.1.20:8443/openclaw-gw",
        urls: ["wss://192.168.1.20:8443/openclaw-gw", "wss://gateway.tailnet.example/tailnet-gw"],
        bootstrapToken: "bootstrap-123",
        tlsFingerprint: `sha256:${TLS_FINGERPRINT.toUpperCase()}`,
      }),
    );

    expect(resolveNodeGatewayOptions({}, null, pair).gatewayCandidates).toEqual([
      {
        host: "192.168.1.20",
        port: 8443,
        contextPath: "/openclaw-gw",
        tls: true,
        tlsFingerprint: TLS_FINGERPRINT,
      },
      {
        host: "gateway.tailnet.example",
        port: 443,
        contextPath: "/tailnet-gw",
        tls: true,
      },
    ]);
    expect(resolveNodeGatewayOptions({}, null, pair).contextPath).toBe("/openclaw-gw");
  });

  it("keeps origin-only pairing endpoints pathless", () => {
    const pair = resolveNodePairGatewayOptions(
      encodePairingSetupCode({
        url: "wss://gateway.example",
        bootstrapToken: "bootstrap-123",
      }),
    );

    expect(resolveNodeGatewayOptions({}, null, pair)).toMatchObject({
      contextPath: undefined,
      gatewayCandidates: [{ host: "gateway.example", port: 443, tls: true }],
    });
  });

  it("collapses pairing candidates when an endpoint flag is explicit", () => {
    const pair = resolveNodePairGatewayOptions(
      encodePairingSetupCode({
        url: "ws://192.168.1.20:18789",
        urls: ["ws://192.168.1.20:18789", "wss://gateway.tailnet.example"],
        bootstrapToken: "bootstrap-123",
      }),
    );

    expect(resolveNodeGatewayOptions({ host: "manual.example" }, null, pair)).toMatchObject({
      host: "manual.example",
      gatewayCandidates: undefined,
    });
  });

  it("canonicalizes explicit TLS pins and rejects invalid values", () => {
    const colonFingerprint = (TLS_FINGERPRINT.match(/.{2}/gu)?.join(":") ?? "").toUpperCase();
    expect(
      resolveNodeGatewayOptions({ tlsFingerprint: `SHA256:${colonFingerprint}` }, null),
    ).toMatchObject({ tls: true, tlsFingerprint: TLS_FINGERPRINT });
    expect(() => resolveNodeGatewayOptions({ tlsFingerprint: "abc123" }, null)).toThrow(
      "Invalid TLS fingerprint",
    );
  });
});