File size: 3,921 Bytes
fcd8223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
87
88
89
90
91
92
import fs from "node:fs/promises";
import { afterEach, describe, expect, it } from "vitest";
import {
  loadDeviceIdentityIfPresent,
  loadOrCreateDeviceIdentity,
} from "../infra/device-identity.js";
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
import {
  createOpenClawTestState,
  type OpenClawTestState,
} from "../test-utils/openclaw-test-state.js";
import { configureNodeHost } from "./config.js";
import { resolveLocalNodeId } from "./local-id.js";

const states: OpenClawTestState[] = [];

async function createState(label: string) {
  const state = await createOpenClawTestState({ label, layout: "state-only", applyEnv: false });
  states.push(state);
  return state;
}

afterEach(async () => {
  closeOpenClawStateDatabaseForTest();
  while (states.length > 0) {
    await states.pop()?.cleanup();
  }
});

describe("resolveLocalNodeId", () => {
  it("uses the primary device identity, not the default runner UUID or instance override", async () => {
    const state = await createState("local-node-id");
    const config = await configureNodeHost({
      fallbackDisplayName: "Gateway node",
      gateway: {},
      env: state.env,
    });
    const identity = loadOrCreateDeviceIdentity({ env: state.env });
    expect(config.nodeId).not.toBe(identity.deviceId);
    await expect(resolveLocalNodeId(state.env)).resolves.toBe(identity.deviceId);

    await configureNodeHost({
      nodeId: "replacement-instance",
      fallbackDisplayName: "Gateway node",
      gateway: {},
      env: state.env,
    });
    await expect(resolveLocalNodeId(state.env)).resolves.toBe(identity.deviceId);

    // Once discovered, the same-install identity stays stable until process restart.
    closeOpenClawStateDatabaseForTest();
    await fs.rm(state.statePath("state"), { recursive: true });
    expect(loadDeviceIdentityIfPresent({ env: state.env })).toBeNull();
    await expect(resolveLocalNodeId(state.env)).resolves.toBe(identity.deviceId);
  });

  it("does not create credentials on a miss and discovers an identity created later", async () => {
    const state = await createState("local-node-id-missing");
    await expect(resolveLocalNodeId(state.env)).resolves.toBeNull();
    expect(await fs.readdir(state.stateDir)).toEqual([]);

    await configureNodeHost({ fallbackDisplayName: "Gateway node", gateway: {}, env: state.env });
    await expect(resolveLocalNodeId(state.env)).resolves.toBeNull();
    expect(loadDeviceIdentityIfPresent({ env: state.env })).toBeNull();

    const identity = loadOrCreateDeviceIdentity({ env: state.env });
    await expect(resolveLocalNodeId(state.env)).resolves.toBe(identity.deviceId);
  });

  it("keeps independently created state-directory identities separate", async () => {
    const gateway = await createState("local-node-id-gateway");
    const independent = await createState("local-node-id-independent");
    const gatewayIdentity = loadOrCreateDeviceIdentity({ env: gateway.env });
    const independentIdentity = loadOrCreateDeviceIdentity({ env: independent.env });
    expect(independentIdentity.deviceId).not.toBe(gatewayIdentity.deviceId);

    await expect(resolveLocalNodeId(gateway.env)).resolves.toBe(gatewayIdentity.deviceId);
    await expect(resolveLocalNodeId(independent.env)).resolves.toBe(independentIdentity.deviceId);
    await expect(resolveLocalNodeId(gateway.env)).resolves.toBe(gatewayIdentity.deviceId);
  });

  it("retries after a failed canonical identity read", async () => {
    const state = await createState("local-node-id-retry");
    const legacyPath = await state.writeText("identity/device.json", "{}\n");
    await expect(resolveLocalNodeId(state.env)).rejects.toThrow("openclaw doctor --fix");

    await fs.rm(legacyPath);
    const identity = loadOrCreateDeviceIdentity({ env: state.env });
    await expect(resolveLocalNodeId(state.env)).resolves.toBe(identity.deviceId);
  });
});