import { describe, expect, it } from "vitest"; import { join } from "node:path"; import { loadConfig } from "../src/config.js"; import { tempRoot } from "./helpers.js"; describe("loadConfig", () => { it("applies safe defaults", () => { const root = tempRoot(); const config = loadConfig({ APP_ROOT: root }); expect(config.port).toBe(8100); expect(config.billing.mode).toBe("live"); expect(config.billing.currency).toBe("credits"); expect(config.gateway.mock).toBe(false); expect(config.gateway.pollTimeoutMs).toBe(900_000); expect(config.llm.modelId).toBe("deepseek-chat"); expect(config.dataDir).toBe(join(root, "data")); expect(config.mysql.host).toBe("127.0.0.1"); expect(config.mysql.database).toBe("laimiaodb"); expect(config.skillDefaults.model).toBe("gpt-image-2"); expect(config.toolConfirmation.enabled).toBe(true); expect(config.toolConfirmation.tools).toEqual(["generate_image"]); expect(config.toolConfirmation.timeoutMs).toBe(300_000); expect(config.modelsFile).toBe(join(root, "data", "pi", "models.json")); }); it("parses booleans and integers from env strings", () => { const config = loadConfig({ APP_ROOT: tempRoot(), PORT: "9001", GATEWAY_MOCK: "TRUE", BILLING_MODE: "off", LAIMIAO_LLM_REASONING: "1", GATEWAY_POLL_INTERVAL_MS: "250", TOOL_CONFIRMATION_TOOLS: "generate_image, quote_image", }); expect(config.port).toBe(9001); expect(config.gateway.mock).toBe(true); expect(config.billing.mode).toBe("off"); expect(config.llm.reasoning).toBe(true); expect(config.gateway.pollIntervalMs).toBe(250); expect(config.toolConfirmation.tools).toEqual(["generate_image", "quote_image"]); }); it("treats an empty integer as the default", () => { const config = loadConfig({ APP_ROOT: tempRoot(), PORT: "" }); expect(config.port).toBe(8100); }); it("rejects invalid port and billing mode", () => { expect(() => loadConfig({ APP_ROOT: tempRoot(), PORT: "0" })).toThrow(/PORT/); expect(() => loadConfig({ APP_ROOT: tempRoot(), BILLING_MODE: "always" })).toThrow(/BILLING_MODE/); }); it("rejects malformed gateway and LLM URLs", () => { expect(() => loadConfig({ APP_ROOT: tempRoot(), GATEWAY_CN_URL: "not-a-url" })).toThrow(/GATEWAY_CN_URL/); expect(() => loadConfig({ APP_ROOT: tempRoot(), LAIMIAO_LLM_BASE_URL: "ftp://x" })).toThrow( /LAIMIAO_LLM_BASE_URL/, ); }); it("enforces production security requirements", () => { const base = { APP_ROOT: tempRoot(), NODE_ENV: "production", JWT_KEY: "secret", CORS_ORIGIN: "https://app.example.com", FILE_FETCH_ALLOWED_HOSTS: "cdn.example.com,*.oss.example.com", }; expect(() => loadConfig({ ...base, JWT_KEY: "" })).toThrow(/JWT_KEY/); expect(() => loadConfig({ ...base, CORS_ORIGIN: "*" })).toThrow(/CORS_ORIGIN/); expect(() => loadConfig({ ...base, CORS_ORIGIN: "https://app.example.com/path" })).toThrow(/CORS_ORIGIN/); expect(() => loadConfig({ ...base, FILE_FETCH_ALLOWED_HOSTS: "" })).toThrow(/FILE_FETCH_ALLOWED_HOSTS/); const config = loadConfig(base); expect(config.isProduction).toBe(true); expect(config.fileFetchAllowedHosts).toEqual(["cdn.example.com", "*.oss.example.com"]); }); it("keeps permissive defaults outside production", () => { const config = loadConfig({ APP_ROOT: tempRoot() }); expect(config.nodeEnv).toBe("development"); expect(config.isProduction).toBe(false); expect(config.fileFetchAllowedHosts).toEqual([]); }); it("resolves relative paths against APP_ROOT", () => { const root = tempRoot(); const config = loadConfig({ APP_ROOT: root, DATA_DIR: "var/state" }); expect(config.dataDir).toBe(join(root, "var/state")); expect(config.ledgerFile).toBe(join(root, "var/state/usage-ledger.jsonl")); }); });