import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { AssetRepository } from "../src/gatekeeper/repository.js"; import { Gatekeeper, GatekeeperGuard } from "../src/gatekeeper/gatekeeper.js"; import { createLogger } from "../src/audit/log.js"; import type { ToolSpec } from "../src/agent/types.js"; const log = createLogger("silent"); const readTool: ToolSpec = { name: "n8n.list_workflows", description: "", parameters: {}, server: "n8n", readOnly: true, riskTier: "reversible", }; const writeTool: ToolSpec = { name: "portainer.deploy_stack", description: "", parameters: {}, server: "portainer", readOnly: false, riskTier: "privileged", }; let repo: AssetRepository; beforeEach(() => { repo = new AssetRepository(":memory:"); }); afterEach(() => { repo.close(); }); describe("Gatekeeper (Phase 2)", () => { it("autorise la lecture seule sans créer d'asset", () => { const gk = new Gatekeeper(repo, log); expect(gk.authorizeTool(readTool).allowed).toBe(true); expect(repo.list()).toHaveLength(0); }); it("refuse un privilégié inconnu, l'enregistre BLOQUÉ et alerte", () => { const onBlockedAttempt = vi.fn(); const gk = new Gatekeeper(repo, log, { onBlockedAttempt }); const v = gk.authorizeTool(writeTool); expect(v.allowed).toBe(false); const id = Gatekeeper.assetIdForTool(writeTool); const asset = repo.get(id); expect(asset?.status).toBe("bloqué"); expect(asset?.riskTier).toBe("privileged"); expect(asset?.expiresAt).toBeNull(); // aucun sursis expect(onBlockedAttempt).toHaveBeenCalledOnce(); }); it("autorise un privilégié APPROUVÉ et incrémente le compteur", () => { const gk = new Gatekeeper(repo, log); gk.authorizeTool(writeTool); // crée l'asset bloqué const id = Gatekeeper.assetIdForTool(writeTool); repo.updateStatus(id, "approuvé"); // review humaine expect(gk.authorizeTool(writeTool).allowed).toBe(true); expect(gk.authorizeTool(writeTool).allowed).toBe(true); expect(repo.get(id)?.execCount).toBe(2); }); it("refuse un asset REFUSÉ", () => { const gk = new Gatekeeper(repo, log); gk.authorizeTool(writeTool); const id = Gatekeeper.assetIdForTool(writeTool); repo.updateStatus(id, "refusé"); expect(gk.authorizeTool(writeTool).allowed).toBe(false); }); it("GatekeeperGuard délègue au gatekeeper", () => { const guard = new GatekeeperGuard(new Gatekeeper(repo, log)); expect(guard.authorize(readTool).allowed).toBe(true); expect(guard.authorize(writeTool).allowed).toBe(false); }); });