260 lines
9.4 KiB
TypeScript
260 lines
9.4 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { withEnvOverride, withTempHome } from "./test-helpers.js";
|
|
|
|
describe("Nix integration (U3, U5, U9)", () => {
|
|
describe("U3: isNixMode env var detection", () => {
|
|
it("isNixMode is false when OPENCLAW_NIX_MODE is not set", async () => {
|
|
await withEnvOverride({ OPENCLAW_NIX_MODE: undefined }, async () => {
|
|
const { isNixMode } = await import("./config.js");
|
|
expect(isNixMode).toBe(false);
|
|
});
|
|
});
|
|
|
|
it("isNixMode is false when OPENCLAW_NIX_MODE is empty", async () => {
|
|
await withEnvOverride({ OPENCLAW_NIX_MODE: "" }, async () => {
|
|
const { isNixMode } = await import("./config.js");
|
|
expect(isNixMode).toBe(false);
|
|
});
|
|
});
|
|
|
|
it("isNixMode is false when OPENCLAW_NIX_MODE is not '1'", async () => {
|
|
await withEnvOverride({ OPENCLAW_NIX_MODE: "true" }, async () => {
|
|
const { isNixMode } = await import("./config.js");
|
|
expect(isNixMode).toBe(false);
|
|
});
|
|
});
|
|
|
|
it("isNixMode is true when OPENCLAW_NIX_MODE=1", async () => {
|
|
await withEnvOverride({ OPENCLAW_NIX_MODE: "1" }, async () => {
|
|
const { isNixMode } = await import("./config.js");
|
|
expect(isNixMode).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("U5: CONFIG_PATH and STATE_DIR env var overrides", () => {
|
|
it("STATE_DIR defaults to ~/.openclaw when env not set", async () => {
|
|
await withEnvOverride({ OPENCLAW_STATE_DIR: undefined }, async () => {
|
|
const { STATE_DIR } = await import("./config.js");
|
|
expect(STATE_DIR).toMatch(/\.openclaw$/);
|
|
});
|
|
});
|
|
|
|
it("STATE_DIR respects OPENCLAW_STATE_DIR override", async () => {
|
|
await withEnvOverride({ OPENCLAW_STATE_DIR: "/custom/state/dir" }, async () => {
|
|
const { STATE_DIR } = await import("./config.js");
|
|
expect(STATE_DIR).toBe(path.resolve("/custom/state/dir"));
|
|
});
|
|
});
|
|
|
|
it("CONFIG_PATH defaults to ~/.openclaw/openclaw.json when env not set", async () => {
|
|
await withEnvOverride(
|
|
{ OPENCLAW_CONFIG_PATH: undefined, OPENCLAW_STATE_DIR: undefined },
|
|
async () => {
|
|
const { CONFIG_PATH } = await import("./config.js");
|
|
expect(CONFIG_PATH).toMatch(/\.openclaw[\\/]openclaw\.json$/);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("CONFIG_PATH respects OPENCLAW_CONFIG_PATH override", async () => {
|
|
await withEnvOverride({ OPENCLAW_CONFIG_PATH: "/nix/store/abc/openclaw.json" }, async () => {
|
|
const { CONFIG_PATH } = await import("./config.js");
|
|
expect(CONFIG_PATH).toBe(path.resolve("/nix/store/abc/openclaw.json"));
|
|
});
|
|
});
|
|
|
|
it("CONFIG_PATH expands ~ in OPENCLAW_CONFIG_PATH override", async () => {
|
|
await withTempHome(async (home) => {
|
|
await withEnvOverride({ OPENCLAW_CONFIG_PATH: "~/.openclaw/custom.json" }, async () => {
|
|
const { CONFIG_PATH } = await import("./config.js");
|
|
expect(CONFIG_PATH).toBe(path.join(home, ".openclaw", "custom.json"));
|
|
});
|
|
});
|
|
});
|
|
|
|
it("CONFIG_PATH uses STATE_DIR when only state dir is overridden", async () => {
|
|
await withEnvOverride(
|
|
{
|
|
OPENCLAW_CONFIG_PATH: undefined,
|
|
OPENCLAW_STATE_DIR: "/custom/state",
|
|
},
|
|
async () => {
|
|
const { CONFIG_PATH } = await import("./config.js");
|
|
expect(CONFIG_PATH).toBe(path.join(path.resolve("/custom/state"), "openclaw.json"));
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("U5b: tilde expansion for config paths", () => {
|
|
it("expands ~ in common path-ish config fields", async () => {
|
|
await withTempHome(async (home) => {
|
|
const configDir = path.join(home, ".openclaw");
|
|
await fs.mkdir(configDir, { recursive: true });
|
|
const pluginDir = path.join(home, "plugins", "demo-plugin");
|
|
await fs.mkdir(pluginDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(pluginDir, "index.js"),
|
|
'export default { id: "demo-plugin", register() {} };',
|
|
"utf-8",
|
|
);
|
|
await fs.writeFile(
|
|
path.join(pluginDir, "openclaw.plugin.json"),
|
|
JSON.stringify(
|
|
{
|
|
id: "demo-plugin",
|
|
configSchema: { type: "object", additionalProperties: false, properties: {} },
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
"utf-8",
|
|
);
|
|
await fs.writeFile(
|
|
path.join(configDir, "openclaw.json"),
|
|
JSON.stringify(
|
|
{
|
|
plugins: {
|
|
load: {
|
|
paths: ["~/plugins/demo-plugin"],
|
|
},
|
|
},
|
|
agents: {
|
|
defaults: { workspace: "~/ws-default" },
|
|
list: [
|
|
{
|
|
id: "main",
|
|
workspace: "~/ws-agent",
|
|
agentDir: "~/.openclaw/agents/main",
|
|
sandbox: { workspaceRoot: "~/sandbox-root" },
|
|
},
|
|
],
|
|
},
|
|
channels: {
|
|
whatsapp: {
|
|
accounts: {
|
|
personal: {
|
|
authDir: "~/.openclaw/credentials/wa-personal",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
"utf-8",
|
|
);
|
|
|
|
vi.resetModules();
|
|
const { loadConfig } = await import("./config.js");
|
|
const cfg = loadConfig();
|
|
|
|
expect(cfg.plugins?.load?.paths?.[0]).toBe(path.join(home, "plugins", "demo-plugin"));
|
|
expect(cfg.agents?.defaults?.workspace).toBe(path.join(home, "ws-default"));
|
|
expect(cfg.agents?.list?.[0]?.workspace).toBe(path.join(home, "ws-agent"));
|
|
expect(cfg.agents?.list?.[0]?.agentDir).toBe(
|
|
path.join(home, ".openclaw", "agents", "main"),
|
|
);
|
|
expect(cfg.agents?.list?.[0]?.sandbox?.workspaceRoot).toBe(path.join(home, "sandbox-root"));
|
|
expect(cfg.channels?.whatsapp?.accounts?.personal?.authDir).toBe(
|
|
path.join(home, ".openclaw", "credentials", "wa-personal"),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("U6: gateway port resolution", () => {
|
|
it("uses default when env and config are unset", async () => {
|
|
await withEnvOverride({ OPENCLAW_GATEWAY_PORT: undefined }, async () => {
|
|
const { DEFAULT_GATEWAY_PORT, resolveGatewayPort } = await import("./config.js");
|
|
expect(resolveGatewayPort({})).toBe(DEFAULT_GATEWAY_PORT);
|
|
});
|
|
});
|
|
|
|
it("prefers OPENCLAW_GATEWAY_PORT over config", async () => {
|
|
await withEnvOverride({ OPENCLAW_GATEWAY_PORT: "19001" }, async () => {
|
|
const { resolveGatewayPort } = await import("./config.js");
|
|
expect(resolveGatewayPort({ gateway: { port: 19002 } })).toBe(19001);
|
|
});
|
|
});
|
|
|
|
it("falls back to config when env is invalid", async () => {
|
|
await withEnvOverride({ OPENCLAW_GATEWAY_PORT: "nope" }, async () => {
|
|
const { resolveGatewayPort } = await import("./config.js");
|
|
expect(resolveGatewayPort({ gateway: { port: 19003 } })).toBe(19003);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("U9: telegram.tokenFile schema validation", () => {
|
|
it("accepts config with only botToken", async () => {
|
|
await withTempHome(async (home) => {
|
|
const configDir = path.join(home, ".openclaw");
|
|
await fs.mkdir(configDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(configDir, "openclaw.json"),
|
|
JSON.stringify({
|
|
channels: { telegram: { botToken: "123:ABC" } },
|
|
}),
|
|
"utf-8",
|
|
);
|
|
|
|
vi.resetModules();
|
|
const { loadConfig } = await import("./config.js");
|
|
const cfg = loadConfig();
|
|
expect(cfg.channels?.telegram?.botToken).toBe("123:ABC");
|
|
expect(cfg.channels?.telegram?.tokenFile).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
it("accepts config with only tokenFile", async () => {
|
|
await withTempHome(async (home) => {
|
|
const configDir = path.join(home, ".openclaw");
|
|
await fs.mkdir(configDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(configDir, "openclaw.json"),
|
|
JSON.stringify({
|
|
channels: { telegram: { tokenFile: "/run/agenix/telegram-token" } },
|
|
}),
|
|
"utf-8",
|
|
);
|
|
|
|
vi.resetModules();
|
|
const { loadConfig } = await import("./config.js");
|
|
const cfg = loadConfig();
|
|
expect(cfg.channels?.telegram?.tokenFile).toBe("/run/agenix/telegram-token");
|
|
expect(cfg.channels?.telegram?.botToken).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
it("accepts config with both botToken and tokenFile", async () => {
|
|
await withTempHome(async (home) => {
|
|
const configDir = path.join(home, ".openclaw");
|
|
await fs.mkdir(configDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(configDir, "openclaw.json"),
|
|
JSON.stringify({
|
|
channels: {
|
|
telegram: {
|
|
botToken: "fallback:token",
|
|
tokenFile: "/run/agenix/telegram-token",
|
|
},
|
|
},
|
|
}),
|
|
"utf-8",
|
|
);
|
|
|
|
vi.resetModules();
|
|
const { loadConfig } = await import("./config.js");
|
|
const cfg = loadConfig();
|
|
expect(cfg.channels?.telegram?.botToken).toBe("fallback:token");
|
|
expect(cfg.channels?.telegram?.tokenFile).toBe("/run/agenix/telegram-token");
|
|
});
|
|
});
|
|
});
|
|
});
|