mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-06 23:01:08 +00:00
229 lines
7.6 KiB
TypeScript
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();
|
|
});
|
|
});
|