File size: 5,044 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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import fs from "node:fs";
import path from "node:path";
import { afterEach, beforeEach, vi } from "vitest";
import { createTempDirTracker } from "../../../test/helpers/temp-dir.js";
import * as tempRoot from "../../infra/tmp-openclaw-dir.js";
import * as packageMetadata from "../../infra/update-check-package-target.js";
import * as updateCheck from "../../infra/update-check.js";
import { createFreeBsdPkgOwnershipInspection } from "../../infra/update-freebsd-pkg-ownership.js";
import * as updateGlobal from "../../infra/update-global.js";
import { defaultRuntime } from "../../runtime.js";
import * as processIdentity from "../../shared/pid-alive.js";
import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js";
import { resolveOpenClawStateSqlitePath } from "../../state/openclaw-state-db.paths.js";
import { captureTargetDatabaseSchemaContext } from "./schema-preflight.js";
import * as shared from "./shared.js";
import * as databaseContext from "./update-command-database-context.js";
import * as packageUpdate from "./update-command-package.js";
import * as commandRun from "./update-command-run.js";
import * as servicePlan from "./update-command-service-plan.js";

export const targetMetadata = {
  target: "2026.9.2",
  version: "2026.9.2",
  nodeEngine: null,
  schemaVersions: { state: 16, agent: 19 },
};

export function installFreshUpdateFixture() {
  const dirs = createTempDirTracker();
  const fixture: { root: string; databasePath: string; managedServiceNodeRunner?: string } = {
    root: "",
    databasePath: "",
  };
  beforeEach(() => {
    fixture.managedServiceNodeRunner = undefined;
    const home = dirs.make("openclaw-update-fresh-preview-");
    fixture.root = path.join(home, "installation");
    fs.mkdirSync(fixture.root);
    fs.writeFileSync(
      path.join(fixture.root, "package.json"),
      JSON.stringify({ name: "openclaw", version: "2026.9.3" }),
    );
    vi.stubEnv("HOME", home);
    vi.stubEnv("OPENCLAW_STATE_DIR", path.join(home, "profile"));
    vi.stubEnv("OPENCLAW_CONFIG_PATH", path.join(home, "profile", "openclaw.json"));
    vi.stubEnv("OPENCLAW_UPDATE_RUN_ID", undefined);
    vi.stubEnv("OPENCLAW_UPDATE_RUN_HANDOFF", undefined);
    vi.stubEnv("OPENCLAW_UPDATE_POST_CORE", undefined);
    fixture.databasePath = resolveOpenClawStateSqlitePath(process.env);
    const executorRoot = path.join(home, "executor");
    fs.mkdirSync(executorRoot);
    vi.spyOn(tempRoot, "resolvePreferredOpenClawTmpDir").mockReturnValue(executorRoot);
    // The macOS test sandbox denies /bin/ps. Keep real lease ownership and liveness,
    // supplying only the stable self identity that the OS probe cannot read here.
    vi.spyOn(processIdentity, "getFileLockProcessStartTime").mockImplementation((pid) =>
      pid === process.pid ? 1_700_000_000 : null,
    );
    vi.spyOn(defaultRuntime, "writeJson").mockImplementation(() => undefined);
    vi.spyOn(defaultRuntime, "error").mockImplementation(() => undefined);
    vi.spyOn(commandRun, "prepareUpdateCommand").mockImplementation(async (opts) => ({
      startedAt: Date.now(),
      postCoreUpdateResume: false,
      postCoreUpdateChannel: undefined,
      timeoutMs: 5_000,
      shouldRestart: opts.restart !== false,
      requestedChannel: opts.channel === "stable" ? "stable" : null,
      devTarget: undefined,
      controlPlaneUpdateSentinelMeta: null,
      discoveredRoot: fixture.root,
      installKind: "package",
      servicePlan: { rootRedirect: null, nodeRunner: fixture.managedServiceNodeRunner },
      pkgOwnership: createFreeBsdPkgOwnershipInspection(5_000),
    }));
    vi.spyOn(servicePlan, "isGatewayServiceManagementAllowedForUpdate").mockReturnValue(false);
    vi.spyOn(databaseContext, "inspectUpdateDatabaseContexts").mockImplementation(async () => ({
      service: undefined,
      services: new Map(),
      contexts: [await captureTargetDatabaseSchemaContext(process.env)],
      managedEnv: undefined,
    }));
    vi.spyOn(shared, "resolveGlobalManager").mockResolvedValue("npm");
    vi.spyOn(shared, "resolveTargetVersion").mockResolvedValue("2026.9.2");
    vi.spyOn(updateGlobal, "createGlobalInstallEnv").mockResolvedValue({ ...process.env });
    vi.spyOn(updateGlobal, "resolveGlobalInstallTarget").mockResolvedValue({
      manager: "npm",
      command: "npm",
      globalRoot: path.dirname(fixture.root),
      packageRoot: fixture.root,
      npmOwner: { version: "11.10.0", lifecyclePolicy: "unflagged" },
    });
    vi.spyOn(updateCheck, "resolveNpmChannelTag").mockResolvedValue({
      tag: "latest",
      version: "2026.9.2",
    });
    vi.spyOn(packageMetadata, "fetchNpmPackageTargetStatus").mockResolvedValue(targetMetadata);
    vi.spyOn(packageUpdate, "stagePackageInstallUpdate").mockRejectedValue(
      new Error("Read-only update admission must not stage a package"),
    );
  });

  afterEach(() => {
    closeOpenClawStateDatabaseForTest();
    vi.restoreAllMocks();
    vi.unstubAllEnvs();
    dirs.cleanup();
  });

  return { fixture, dirs };
}