feat: GitCommitter + AutoExtensionService (v0.26.0)
propose() : écrit artefact+doc → commit ciblé + version → enregistre l'asset en need-review (privilégié = BLOQUÉ sans sursis) → alerte asset_created (version+commit+doc). N'exécute jamais l'asset. Commits ciblés (jamais add -A). 73 tests (dépôt git temp), 0 vuln. Palier de risque : privilégié (écrit + commit dans le dépôt) — derrière flag + Phase 2 ; n'exécute aucun asset, tout reste sous review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { mkdtemp, rm, access } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { AssetRepository } from "../src/gatekeeper/repository.js";
|
||||
import { GitCommitter } from "../src/autoext/git-committer.js";
|
||||
import { AutoExtensionService } from "../src/autoext/auto-extension.js";
|
||||
import { createLogger } from "../src/audit/log.js";
|
||||
import type { AlertSender, AlertEvent } from "../src/alerts/types.js";
|
||||
import type { AssetDraft } from "../src/autoext/artifact-writer.js";
|
||||
|
||||
const exec = promisify(execFile);
|
||||
const log = createLogger("silent");
|
||||
|
||||
class CapturingSender implements AlertSender {
|
||||
events: AlertEvent[] = [];
|
||||
async send(e: AlertEvent): Promise<void> {
|
||||
this.events.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
const draft = (over: Partial<AssetDraft> = {}): AssetDraft => ({
|
||||
type: "workflow-n8n",
|
||||
name: "Backup NC",
|
||||
version: "1.0.0",
|
||||
riskTier: "privileged",
|
||||
summary: "Sauvegarde.",
|
||||
content: '{"a":1}',
|
||||
...over,
|
||||
});
|
||||
|
||||
let root: string;
|
||||
let repo: AssetRepository;
|
||||
|
||||
beforeEach(async () => {
|
||||
root = await mkdtemp(join(tmpdir(), "chlova-git-"));
|
||||
await exec("git", ["init", "-q"], { cwd: root });
|
||||
await exec("git", ["-c", "user.name=t", "-c", "user.email=t@t", "commit", "--allow-empty", "-q", "-m", "init"], { cwd: root });
|
||||
repo = new AssetRepository(":memory:");
|
||||
});
|
||||
afterEach(async () => {
|
||||
repo.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function service(alerts: AlertSender): AutoExtensionService {
|
||||
return new AutoExtensionService(repo, new GitCommitter(root), alerts, log, root);
|
||||
}
|
||||
|
||||
describe("AutoExtensionService.propose", () => {
|
||||
it("écrit, commit, enregistre BLOQUÉ (privilégié) et alerte", async () => {
|
||||
const sender = new CapturingSender();
|
||||
const res = await service(sender).propose(draft());
|
||||
|
||||
expect(res.asset.status).toBe("bloqué"); // privilégié → aucun sursis
|
||||
expect(res.commit).toMatch(/^[0-9a-f]{7,}$/);
|
||||
expect(res.asset.commitLink).toBe(res.commit);
|
||||
expect(res.asset.docLink).toBe(res.docPath);
|
||||
|
||||
// fichiers présents + commités
|
||||
await expect(access(join(root, res.artifactPath))).resolves.toBeUndefined();
|
||||
const { stdout } = await exec("git", ["log", "--oneline"], { cwd: root });
|
||||
expect(stdout).toContain("need-review");
|
||||
|
||||
// asset persistant + alerte
|
||||
expect(repo.get(res.asset.id)?.status).toBe("bloqué");
|
||||
expect(sender.events.map((e) => e.kind)).toContain("asset_created");
|
||||
});
|
||||
|
||||
it("réversible → PROVISOIRE", async () => {
|
||||
const res = await service(new CapturingSender()).propose(draft({ riskTier: "reversible", name: "RO tool", type: "tool" }));
|
||||
expect(res.asset.status).toBe("provisoire");
|
||||
expect(res.asset.expiresAt).not.toBeNull();
|
||||
});
|
||||
|
||||
it("refuse un doublon (même id)", async () => {
|
||||
const svc = service(new CapturingSender());
|
||||
await svc.propose(draft());
|
||||
await expect(svc.propose(draft())).rejects.toThrow(/existant/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user