Files
openclaw/src/config/io.write-config.test.ts
Shadow f59df95896 Config: preserve env var references on write (#15600)
* Config: preserve env var references on write

* Config: handle env refs in arrays
2026-02-13 11:52:23 -06:00

154 lines
4.5 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { createConfigIO } from "./io.js";
import { withTempHome } from "./test-helpers.js";
describe("config io write", () => {
it("persists caller changes onto resolved config without leaking runtime defaults", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
JSON.stringify({ gateway: { port: 18789 } }, null, 2),
"utf-8",
);
const io = createConfigIO({
env: {} as NodeJS.ProcessEnv,
homedir: () => home,
});
const snapshot = await io.readConfigFileSnapshot();
expect(snapshot.valid).toBe(true);
const next = structuredClone(snapshot.config);
next.gateway = {
...next.gateway,
auth: { mode: "token" },
};
await io.writeConfigFile(next);
const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as Record<
string,
unknown
>;
expect(persisted.gateway).toEqual({
port: 18789,
auth: { mode: "token" },
});
expect(persisted).not.toHaveProperty("agents.defaults");
expect(persisted).not.toHaveProperty("messages.ackReaction");
expect(persisted).not.toHaveProperty("sessions.persistence");
});
});
it("preserves env var references when writing", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
JSON.stringify(
{
agents: {
defaults: {
cliBackends: {
codex: {
env: {
OPENAI_API_KEY: "${OPENAI_API_KEY}",
},
},
},
},
},
gateway: { port: 18789 },
},
null,
2,
),
"utf-8",
);
const io = createConfigIO({
env: { OPENAI_API_KEY: "sk-secret" } as NodeJS.ProcessEnv,
homedir: () => home,
});
const snapshot = await io.readConfigFileSnapshot();
expect(snapshot.valid).toBe(true);
const next = structuredClone(snapshot.config);
next.gateway = {
...next.gateway,
auth: { mode: "token" },
};
await io.writeConfigFile(next);
const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as {
agents: { defaults: { cliBackends: { codex: { env: { OPENAI_API_KEY: string } } } } };
gateway: { port: number; auth: { mode: string } };
};
expect(persisted.agents.defaults.cliBackends.codex.env.OPENAI_API_KEY).toBe(
"${OPENAI_API_KEY}",
);
expect(persisted.gateway).toEqual({
port: 18789,
auth: { mode: "token" },
});
});
});
it("keeps env refs in arrays when appending entries", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
JSON.stringify(
{
channels: {
discord: {
allowFrom: ["${DISCORD_USER_ID}", "123"],
},
},
},
null,
2,
),
"utf-8",
);
const io = createConfigIO({
env: { DISCORD_USER_ID: "999" } as NodeJS.ProcessEnv,
homedir: () => home,
});
const snapshot = await io.readConfigFileSnapshot();
expect(snapshot.valid).toBe(true);
const next = structuredClone(snapshot.config);
const allowFrom = Array.isArray(next.channels?.discord?.allowFrom)
? next.channels?.discord?.allowFrom
: [];
next.channels = {
...next.channels,
discord: {
...next.channels?.discord,
allowFrom: [...allowFrom, "456"],
},
};
await io.writeConfigFile(next);
const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as {
channels: { discord?: { allowFrom?: string[] } };
};
expect(persisted.channels.discord?.allowFrom).toEqual(["${DISCORD_USER_ID}", "123", "456"]);
});
});
});