75 lines
3.8 KiB
TypeScript
75 lines
3.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { parseConfig } from "../src/config.js";
|
|
|
|
function raw(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
configVersion: 3,
|
|
bot: {
|
|
id: "test-bot", workspace: "/tmp", persona: "test",
|
|
agent: { id: "kimi", command: "kimi", args: ["acp"], env: {} },
|
|
skills: [], permissions: { mode: "deny", allowedTools: [], allowedCommandPatterns: [] }
|
|
},
|
|
gateway: { platform: { type: "webhook", secret: "secret" } },
|
|
runtime: {},
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
test("parses Config v3 defaults for one Bot, agent, and platform", () => {
|
|
const config = parseConfig(raw());
|
|
assert.equal(config.configVersion, 3);
|
|
assert.equal(config.bot.id, "test-bot");
|
|
assert.equal(config.bot.agent.id, "kimi");
|
|
assert.equal(config.gateway.platform.type, "webhook");
|
|
assert.equal(config.runtime.acp.promptTimeoutMs, 14_400_000);
|
|
assert.equal(config.runtime.acp.maxAssistantSessions, 4);
|
|
assert.equal(config.bot.permissions.mode, "deny");
|
|
assert.equal(config.bot.assistantPersona, "");
|
|
});
|
|
|
|
test("parses optional bot.assistantPersona", () => {
|
|
const config = parseConfig(raw({ bot: { ...(raw().bot as object), assistantPersona: "像运维老同事一样讲话" } }));
|
|
assert.equal(config.bot.assistantPersona, "像运维老同事一样讲话");
|
|
});
|
|
|
|
test("explicitly rejects non-v3 configuration and unknown fields", () => {
|
|
assert.throws(() => parseConfig({ configVersion: 2 }), /requires Config v3/);
|
|
assert.throws(() => parseConfig({ defaultAgent: "kimi" }), /requires Config v3/);
|
|
assert.throws(() => parseConfig(raw({ bot: { ...(raw().bot as object), legacyRole: "ops" } })), /Unrecognized key/);
|
|
assert.throws(() => parseConfig(raw({ gateway: { platform: { type: "webhook", secret: "secret", enabled: true } } })), /Unrecognized key/);
|
|
});
|
|
|
|
test("validates bot ID, absolute workspace, skills, and command regex", () => {
|
|
assert.throws(() => parseConfig(raw({ bot: { ...(raw().bot as object), id: "../bad" } })), /bot\.id/);
|
|
assert.throws(() => parseConfig(raw({ bot: { ...(raw().bot as object), workspace: "relative" } })), /workspace must be absolute/);
|
|
assert.throws(() => parseConfig(raw({ bot: { ...(raw().bot as object), skills: [{ id: "x", file: "relative" }] } })), /file must be absolute/);
|
|
assert.throws(() => parseConfig(raw({ bot: { ...(raw().bot as object), permissions: { mode: "allowlist", allowedTools: ["bash"], allowedCommandPatterns: ["["] } } })), /invalid command pattern/);
|
|
});
|
|
|
|
test("validates gateway server host and port", () => {
|
|
for (const host of ["localhost", "0.0.0.0", "::1", "gateway.example.com"]) {
|
|
assert.equal(parseConfig(raw({ gateway: { server: { host, port: 8787 }, platform: { type: "webhook", secret: "secret" } } })).gateway.server.host, host);
|
|
}
|
|
for (const host of ["http://localhost", "localhost:8787", "[::1]", "bad host", "host/path"]) {
|
|
assert.throws(() => parseConfig(raw({ gateway: { server: { host, port: 8787 }, platform: { type: "webhook", secret: "secret" } } })), /server host/);
|
|
}
|
|
for (const port of [0, 65_536, 1.5]) {
|
|
assert.throws(() => parseConfig(raw({ gateway: { server: { host: "localhost", port }, platform: { type: "webhook", secret: "secret" } } })), /gateway/);
|
|
}
|
|
});
|
|
|
|
test("config.example.json is a parseable, secret-free documentation template", () => {
|
|
const text = fs.readFileSync(path.resolve("config.example.json"), "utf8");
|
|
const config = parseConfig(JSON.parse(text) as unknown);
|
|
assert.equal(config.configVersion, 3);
|
|
assert.equal(config.bot.id, "BOT_ID");
|
|
assert.equal(config.bot.permissions.mode, "deny");
|
|
assert.equal(config.runtime.acp.promptTimeoutMs, 14_400_000);
|
|
assert.equal(config.gateway.platform.type, "qq");
|
|
assert.match(text, /QQ_CLIENT_SECRET/);
|
|
assert.doesNotMatch(text, /configVersion"\s*:\s*[12]/);
|
|
});
|