Files
openclaw/src/cli/run-main.profile-env.test.ts
2026-03-27 22:48:09 +00:00

229 lines
7.6 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const fileState = vi.hoisted(() => ({
hasCliDotEnv: false,
}));
const dotenvState = vi.hoisted(() => {
const state = {
profileAtDotenvLoad: undefined as string | undefined,
containerAtDotenvLoad: undefined as string | undefined,
};
return {
state,
loadDotEnv: vi.fn(() => {
state.profileAtDotenvLoad = process.env.OPENCLAW_PROFILE;
state.containerAtDotenvLoad = process.env.OPENCLAW_CONTAINER;
}),
};
});
const maybeRunCliInContainerMock = vi.hoisted(() =>
vi.fn((argv: string[]) => ({ handled: false, argv })),
);
vi.mock("node:fs", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs")>();
type ExistsSyncPath = Parameters<typeof actual.existsSync>[0];
return {
...actual,
existsSync: vi.fn((target: ExistsSyncPath) => {
if (typeof target === "string" && target.endsWith(".env")) {
return fileState.hasCliDotEnv;
}
return actual.existsSync(target);
}),
};
});
vi.mock("./dotenv.js", () => ({
loadCliDotEnv: dotenvState.loadDotEnv,
}));
vi.mock("../infra/env.js", () => ({
normalizeEnv: vi.fn(),
}));
vi.mock("../infra/runtime-guard.js", () => ({
assertSupportedRuntime: vi.fn(),
}));
vi.mock("../infra/path-env.js", () => ({
ensureOpenClawCliOnPath: vi.fn(),
}));
vi.mock("./route.js", () => ({
tryRouteCli: vi.fn(async () => true),
}));
vi.mock("./windows-argv.js", () => ({
normalizeWindowsArgv: (argv: string[]) => argv,
}));
vi.mock("./container-target.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./container-target.js")>();
return {
...actual,
maybeRunCliInContainer: maybeRunCliInContainerMock,
};
});
import { runCli } from "./run-main.js";
describe("runCli profile env bootstrap", () => {
const originalProfile = process.env.OPENCLAW_PROFILE;
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
const originalConfigPath = process.env.OPENCLAW_CONFIG_PATH;
const originalContainer = process.env.OPENCLAW_CONTAINER;
const originalGatewayPort = process.env.OPENCLAW_GATEWAY_PORT;
const originalGatewayUrl = process.env.OPENCLAW_GATEWAY_URL;
const originalGatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN;
const originalGatewayPassword = process.env.OPENCLAW_GATEWAY_PASSWORD;
beforeEach(() => {
delete process.env.OPENCLAW_PROFILE;
delete process.env.OPENCLAW_STATE_DIR;
delete process.env.OPENCLAW_CONFIG_PATH;
delete process.env.OPENCLAW_CONTAINER;
delete process.env.OPENCLAW_GATEWAY_PORT;
delete process.env.OPENCLAW_GATEWAY_URL;
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
dotenvState.state.profileAtDotenvLoad = undefined;
dotenvState.state.containerAtDotenvLoad = undefined;
dotenvState.loadDotEnv.mockClear();
maybeRunCliInContainerMock.mockClear();
fileState.hasCliDotEnv = false;
});
afterEach(() => {
if (originalProfile === undefined) {
delete process.env.OPENCLAW_PROFILE;
} else {
process.env.OPENCLAW_PROFILE = originalProfile;
}
if (originalContainer === undefined) {
delete process.env.OPENCLAW_CONTAINER;
} else {
process.env.OPENCLAW_CONTAINER = originalContainer;
}
if (originalStateDir === undefined) {
delete process.env.OPENCLAW_STATE_DIR;
} else {
process.env.OPENCLAW_STATE_DIR = originalStateDir;
}
if (originalConfigPath === undefined) {
delete process.env.OPENCLAW_CONFIG_PATH;
} else {
process.env.OPENCLAW_CONFIG_PATH = originalConfigPath;
}
if (originalGatewayPort === undefined) {
delete process.env.OPENCLAW_GATEWAY_PORT;
} else {
process.env.OPENCLAW_GATEWAY_PORT = originalGatewayPort;
}
if (originalGatewayUrl === undefined) {
delete process.env.OPENCLAW_GATEWAY_URL;
} else {
process.env.OPENCLAW_GATEWAY_URL = originalGatewayUrl;
}
if (originalGatewayToken === undefined) {
delete process.env.OPENCLAW_GATEWAY_TOKEN;
} else {
process.env.OPENCLAW_GATEWAY_TOKEN = originalGatewayToken;
}
if (originalGatewayPassword === undefined) {
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
} else {
process.env.OPENCLAW_GATEWAY_PASSWORD = originalGatewayPassword;
}
});
it("applies --profile before dotenv loading", async () => {
fileState.hasCliDotEnv = true;
await runCli(["node", "openclaw", "--profile", "rawdog", "status"]);
expect(dotenvState.loadDotEnv).toHaveBeenCalledOnce();
expect(dotenvState.state.profileAtDotenvLoad).toBe("rawdog");
expect(process.env.OPENCLAW_PROFILE).toBe("rawdog");
});
it("rejects --container combined with --profile", async () => {
await expect(
runCli(["node", "openclaw", "--container", "demo", "--profile", "rawdog", "status"]),
).rejects.toThrow("--container cannot be combined with --profile/--dev");
expect(dotenvState.loadDotEnv).not.toHaveBeenCalled();
expect(process.env.OPENCLAW_PROFILE).toBe("rawdog");
});
it("rejects --container combined with interleaved --profile", async () => {
await expect(
runCli(["node", "openclaw", "status", "--container", "demo", "--profile", "rawdog"]),
).rejects.toThrow("--container cannot be combined with --profile/--dev");
});
it("rejects --container combined with interleaved --dev", async () => {
await expect(
runCli(["node", "openclaw", "status", "--container", "demo", "--dev"]),
).rejects.toThrow("--container cannot be combined with --profile/--dev");
});
it("does not let dotenv change container target resolution", async () => {
fileState.hasCliDotEnv = true;
dotenvState.loadDotEnv.mockImplementationOnce(() => {
process.env.OPENCLAW_CONTAINER = "demo";
dotenvState.state.profileAtDotenvLoad = process.env.OPENCLAW_PROFILE;
dotenvState.state.containerAtDotenvLoad = process.env.OPENCLAW_CONTAINER;
});
await runCli(["node", "openclaw", "status"]);
expect(dotenvState.loadDotEnv).toHaveBeenCalledOnce();
expect(process.env.OPENCLAW_CONTAINER).toBe("demo");
expect(dotenvState.state.containerAtDotenvLoad).toBe("demo");
expect(maybeRunCliInContainerMock).toHaveBeenCalledWith(["node", "openclaw", "status"]);
expect(maybeRunCliInContainerMock).toHaveReturnedWith({
handled: false,
argv: ["node", "openclaw", "status"],
});
});
it("allows container mode when OPENCLAW_PROFILE is already set in env", async () => {
process.env.OPENCLAW_PROFILE = "work";
await expect(
runCli(["node", "openclaw", "--container", "demo", "status"]),
).resolves.toBeUndefined();
});
it.each([
["OPENCLAW_GATEWAY_PORT", "19001"],
["OPENCLAW_GATEWAY_URL", "ws://127.0.0.1:18789"],
["OPENCLAW_GATEWAY_TOKEN", "demo-token"],
["OPENCLAW_GATEWAY_PASSWORD", "demo-password"],
])("allows container mode when %s is set in env", async (key, value) => {
process.env[key] = value;
await expect(
runCli(["node", "openclaw", "--container", "demo", "status"]),
).resolves.toBeUndefined();
});
it("allows container mode when only OPENCLAW_STATE_DIR is set in env", async () => {
process.env.OPENCLAW_STATE_DIR = "/tmp/openclaw-host-state";
await expect(
runCli(["node", "openclaw", "--container", "demo", "status"]),
).resolves.toBeUndefined();
});
it("allows container mode when only OPENCLAW_CONFIG_PATH is set in env", async () => {
process.env.OPENCLAW_CONFIG_PATH = "/tmp/openclaw-host-state/openclaw.json";
await expect(
runCli(["node", "openclaw", "--container", "demo", "status"]),
).resolves.toBeUndefined();
});
});