File size: 2,894 Bytes
eb3f11e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
// Update restart handoff tests cover system-domain ownership checks at execution time.
import { execFile } from "node:child_process";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { afterEach, describe, expect, it } from "vitest";
import { prepareRestartScript } from "./restart-helper.js";

const execFileAsync = promisify(execFile);

describe("macOS update restart system ownership", () => {
  const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
  const originalGetuid = process.getuid;

  afterEach(() => {
    if (originalPlatformDescriptor) {
      Object.defineProperty(process, "platform", originalPlatformDescriptor);
    }
    process.getuid = originalGetuid;
  });

  it("refuses the detached handoff before user activation when a system owner appears", async () => {
    Object.defineProperty(process, "platform", {
      ...originalPlatformDescriptor,
      value: "darwin",
    });
    process.getuid = () => 501;
    const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-restart-system-"));
    const fakeBinDir = path.join(tmpDir, "bin");
    const stateDir = path.join(tmpDir, "state");
    const activationMarker = path.join(tmpDir, "activation-ran");
    await fs.mkdir(fakeBinDir, { recursive: true });
    await fs.writeFile(path.join(fakeBinDir, "sleep"), "#!/bin/sh\nexit 0\n", { mode: 0o755 });
    await fs.writeFile(
      path.join(fakeBinDir, "launchctl"),
      `#!/bin/sh
if [ "$1" = "print" ] && [ "$2" = "system/ai.openclaw.gateway" ]; then
  exit 0
fi
printf activated > "$ACTIVATION_MARKER"
exit 0
`,
      { mode: 0o755 },
    );

    try {
      const scriptPath = await prepareRestartScript({
        OPENCLAW_PROFILE: "default",
        HOME: path.join(tmpDir, "home"),
        OPENCLAW_STATE_DIR: stateDir,
      });
      if (!scriptPath) {
        throw new Error("expected restart script path");
      }
      let exitCode: number | null = null;
      try {
        await execFileAsync("/bin/sh", [scriptPath], {
          env: {
            ...process.env,
            ACTIVATION_MARKER: activationMarker,
            PATH: `${fakeBinDir}:${process.env.PATH ?? ""}`,
          },
        });
      } catch (error) {
        const code = (error as { code?: unknown }).code;
        exitCode = typeof code === "number" ? code : null;
      }
      const log = await fs.readFile(path.join(stateDir, "logs", "gateway-restart.log"), "utf8");

      expect(exitCode).toBe(78);
      await expect(fs.access(activationMarker)).rejects.toMatchObject({ code: "ENOENT" });
      expect(log).toContain("openclaw restart blocked source=update");
      expect(log).toContain("loaded system LaunchDaemon system/ai.openclaw.gateway");
    } finally {
      await fs.rm(tmpDir, { recursive: true, force: true });
    }
  });
});