import { describe, it, expect } from "vitest"; import { loadConfig, assertReadOnlyPhase, redactedConfig } from "../src/config.js"; const fullEnv = (): NodeJS.ProcessEnv => ({ OLLAMA_BASE_URL: "http://ollama:11434", OLLAMA_API_KEY: "secret-ollama", OLLAMA_MODEL: "qwen3:cloud", MCP_N8N_URL: "http://mcp-n8n:3000", MCP_N8N_AUTH_TOKEN: "secret-n8n", MCP_PORTAINER_URL: "http://mcp-portainer:3000", PORTAINER_MCP_AUTH_TOKEN: "secret-portainer", PORTAINER_READ_ONLY: "true", TELEGRAM_BOT_TOKEN: "secret-tg", TELEGRAM_ALLOWED_USER_IDS: "111, 222", }); describe("config fail-closed", () => { it("charge une config complète", () => { const cfg = loadConfig(fullEnv()); expect(cfg.ollamaModel).toBe("qwen3:cloud"); expect(cfg.telegramAllowedUserIds).toEqual(["111", "222"]); }); it("refuse de démarrer si un secret manque", () => { const env = fullEnv(); delete env.OLLAMA_API_KEY; expect(() => loadConfig(env)).toThrow(/fail-closed/); }); it("refuse une URL invalide", () => { const env = fullEnv(); env.MCP_N8N_URL = "pas-une-url"; expect(() => loadConfig(env)).toThrow(); }); }); describe("verrou lecture seule Phase 1", () => { it("accepte PORTAINER_READ_ONLY=true", () => { expect(() => assertReadOnlyPhase(loadConfig(fullEnv()))).not.toThrow(); }); it("refuse PORTAINER_READ_ONLY=false", () => { const env = fullEnv(); env.PORTAINER_READ_ONLY = "false"; expect(() => assertReadOnlyPhase(loadConfig(env))).toThrow(/lecture seule/); }); }); describe("redactedConfig masque les secrets", () => { it("ne révèle aucun secret", () => { const red = redactedConfig(loadConfig(fullEnv())); expect(red.ollamaApiKey).toBe("[REDACTED]"); expect(red.telegramBotToken).toBe("[REDACTED]"); expect(red.mcpN8nAuthToken).toBe("[REDACTED]"); expect(red.portainerMcpAuthToken).toBe("[REDACTED]"); // les non-secrets restent visibles expect(red.ollamaModel).toBe("qwen3:cloud"); }); });