test: gatekeeper + readonly-filter + config, interfaces need-review (v0.9.0)
Fin Phase 1. 22 tests verts : barrière readonly-filter (fail-safe), ReadOnlyGuard, paliers de risque + sursis, invariant anti-escalade, config fail-closed + masquage secrets. Interfaces du cycle need-review posées pour la Phase 2 (Asset, canExecute) sans câblage runtime. Split tsconfig typecheck/build. Palier de risque : reversible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user