import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { parseConfig } from "../src/config.js"; import { assertOperationalConfig, loadConfigFile, writeConfigFile } from "../src/cli/config-file.js"; const config = parseConfig({ configVersion: 3, bot: { id: "file-bot", workspace: "/tmp", persona: "", agent: { id: "node", command: process.execPath, args: ["acp"] }, permissions: { mode: "deny" } }, gateway: { platform: { type: "webhook", secret: "secret" } }, runtime: {} }); test("runtime config loading fails closed instead of seeding example", () => { const missing = path.join(os.tmpdir(), `gori-missing-${Date.now()}`, "config.json"); assert.throws(() => loadConfigFile(missing), /documentation only/); }); test("config writes are atomic and mode 0600", async () => { const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "gori-config-write-")); const file = path.join(dir, "config.json"); writeConfigFile(file, config); assert.equal((await fs.promises.stat(dir)).mode & 0o777, 0o700); assert.equal((await fs.promises.stat(file)).mode & 0o777, 0o600); assert.equal(loadConfigFile(file).config.bot.id, "file-bot"); assert.equal((await fs.promises.readdir(dir)).some((name) => name.endsWith(".tmp")), false); }); test("operational validation rejects placeholders, unsafe ACP args, and every credential family", () => { assert.doesNotThrow(() => assertOperationalConfig(config)); const unsafeArgs = parseConfig({ ...config, bot: { ...config.bot, agent: { ...config.bot.agent, args: ["--yolo", "acp"] } } }); assert.throws(() => assertOperationalConfig(unsafeArgs), /exactly \['acp'\]/); const platforms = [ { type: "qq", appId: "QQ_APP_ID", clientSecret: "QQ_CLIENT_SECRET", botNames: ["QQ_BOT_NAME"] }, { type: "feishu", appId: "FEISHU_APP_ID", appSecret: "FEISHU_APP_SECRET" }, { type: "wecom", corpId: "WECOM_CORP_ID", agentId: "WECOM_AGENT_ID", secret: "WECOM_SECRET" }, { type: "webhook", secret: "WEBHOOK_SECRET" }, { type: "weixin", secret: "WEIXIN_SECRET" } ]; for (const platform of platforms) { const candidate = parseConfig({ ...config, gateway: { ...config.gateway, platform } }); assert.throws(() => assertOperationalConfig(candidate), /missing or placeholder/); } });