diff --git a/src/commands/doctor-session-locks.test.ts b/src/commands/doctor-session-locks.test.ts index eb5a656a833..7a89b9437bf 100644 --- a/src/commands/doctor-session-locks.test.ts +++ b/src/commands/doctor-session-locks.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { captureEnv } from "../test-utils/env.js"; const note = vi.hoisted(() => vi.fn()); @@ -13,21 +14,17 @@ import { noteSessionLockHealth } from "./doctor-session-locks.js"; describe("noteSessionLockHealth", () => { let root: string; - let prevStateDir: string | undefined; + let envSnapshot: ReturnType; beforeEach(async () => { note.mockReset(); - prevStateDir = process.env.OPENCLAW_STATE_DIR; + envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]); root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-doctor-locks-")); process.env.OPENCLAW_STATE_DIR = root; }); afterEach(async () => { - if (prevStateDir === undefined) { - delete process.env.OPENCLAW_STATE_DIR; - } else { - process.env.OPENCLAW_STATE_DIR = prevStateDir; - } + envSnapshot.restore(); await fs.rm(root, { recursive: true, force: true }); }); diff --git a/src/infra/restart-sentinel.test.ts b/src/infra/restart-sentinel.test.ts index a675617f948..ec97c8c5c15 100644 --- a/src/infra/restart-sentinel.test.ts +++ b/src/infra/restart-sentinel.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { captureEnv } from "../test-utils/env.js"; import { consumeRestartSentinel, formatRestartSentinelMessage, @@ -12,21 +13,17 @@ import { } from "./restart-sentinel.js"; describe("restart sentinel", () => { - let prevStateDir: string | undefined; + let envSnapshot: ReturnType; let tempDir: string; beforeEach(async () => { - prevStateDir = process.env.OPENCLAW_STATE_DIR; + envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]); tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sentinel-")); process.env.OPENCLAW_STATE_DIR = tempDir; }); afterEach(async () => { - if (prevStateDir) { - process.env.OPENCLAW_STATE_DIR = prevStateDir; - } else { - delete process.env.OPENCLAW_STATE_DIR; - } + envSnapshot.restore(); await fs.rm(tempDir, { recursive: true, force: true }); }); diff --git a/src/infra/update-startup.test.ts b/src/infra/update-startup.test.ts index cc88cc1ce7a..924740cdd33 100644 --- a/src/infra/update-startup.test.ts +++ b/src/infra/update-startup.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { captureEnv } from "../test-utils/env.js"; import type { UpdateCheckResult } from "./update-check.js"; vi.mock("./openclaw-root.js", () => ({ @@ -38,12 +39,7 @@ describe("update-startup", () => { let suiteRoot = ""; let suiteCase = 0; let tempDir: string; - let prevStateDir: string | undefined; - let prevNodeEnv: string | undefined; - let prevVitest: string | undefined; - let hadStateDir = false; - let hadNodeEnv = false; - let hadVitest = false; + let envSnapshot: ReturnType; let resolveOpenClawPackageRoot: (typeof import("./openclaw-root.js"))["resolveOpenClawPackageRoot"]; let checkUpdateStatus: (typeof import("./update-check.js"))["checkUpdateStatus"]; @@ -62,17 +58,12 @@ describe("update-startup", () => { vi.setSystemTime(new Date("2026-01-17T10:00:00Z")); tempDir = path.join(suiteRoot, `case-${++suiteCase}`); await fs.mkdir(tempDir); - hadStateDir = Object.prototype.hasOwnProperty.call(process.env, "OPENCLAW_STATE_DIR"); - prevStateDir = process.env.OPENCLAW_STATE_DIR; + envSnapshot = captureEnv(["OPENCLAW_STATE_DIR", "NODE_ENV", "VITEST"]); process.env.OPENCLAW_STATE_DIR = tempDir; - hadNodeEnv = Object.prototype.hasOwnProperty.call(process.env, "NODE_ENV"); - prevNodeEnv = process.env.NODE_ENV; process.env.NODE_ENV = "test"; // Ensure update checks don't short-circuit in test mode. - hadVitest = Object.prototype.hasOwnProperty.call(process.env, "VITEST"); - prevVitest = process.env.VITEST; delete process.env.VITEST; // Perf: load mocked modules once (after timers/env are set up). @@ -91,21 +82,7 @@ describe("update-startup", () => { afterEach(async () => { vi.useRealTimers(); - if (hadStateDir) { - process.env.OPENCLAW_STATE_DIR = prevStateDir; - } else { - delete process.env.OPENCLAW_STATE_DIR; - } - if (hadNodeEnv) { - process.env.NODE_ENV = prevNodeEnv; - } else { - delete process.env.NODE_ENV; - } - if (hadVitest) { - process.env.VITEST = prevVitest; - } else { - delete process.env.VITEST; - } + envSnapshot.restore(); resetUpdateAvailableStateForTest(); }); diff --git a/src/test-utils/env.test.ts b/src/test-utils/env.test.ts index 07c01c09758..a978c4bc45c 100644 --- a/src/test-utils/env.test.ts +++ b/src/test-utils/env.test.ts @@ -40,6 +40,22 @@ describe("env test utils", () => { expect(process.env[key]).toBe(prev); }); + it("withEnv can delete a key only inside callback", () => { + const key = "OPENCLAW_ENV_TEST_SYNC_DELETE"; + const prev = process.env[key]; + process.env[key] = "outer"; + + const seen = withEnv({ [key]: undefined }, () => process.env[key]); + + expect(seen).toBeUndefined(); + expect(process.env[key]).toBe("outer"); + if (prev === undefined) { + delete process.env[key]; + } else { + process.env[key] = prev; + } + }); + it("withEnvAsync restores values when callback throws", async () => { const key = "OPENCLAW_ENV_TEST_ASYNC"; const prev = process.env[key]; @@ -63,4 +79,20 @@ describe("env test utils", () => { expect(seen).toBe("inside"); expect(process.env[key]).toBe(prev); }); + + it("withEnvAsync can delete a key only inside callback", async () => { + const key = "OPENCLAW_ENV_TEST_ASYNC_DELETE"; + const prev = process.env[key]; + process.env[key] = "outer"; + + const seen = await withEnvAsync({ [key]: undefined }, async () => process.env[key]); + + expect(seen).toBeUndefined(); + expect(process.env[key]).toBe("outer"); + if (prev === undefined) { + delete process.env[key]; + } else { + process.env[key] = prev; + } + }); });