v0.2.0: Git-native architecture (#1)
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
import { EscalationProtocol, EscalationInput } from "../core/escalation.js";
|
||||
import { DEFAULT_CI_CONFIG } from "../types/config.js";
|
||||
|
||||
describe("EscalationProtocol", () => {
|
||||
let tempDir: string;
|
||||
let protocol: EscalationProtocol;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-escalation-test-"));
|
||||
const noAutoCommitConfig = {
|
||||
...DEFAULT_CI_CONFIG,
|
||||
git: { ...DEFAULT_CI_CONFIG.git, auto_commit: false },
|
||||
};
|
||||
protocol = new EscalationProtocol(noAutoCommitConfig, tempDir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
protocol.clearAllTimers();
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const baseInput: EscalationInput = {
|
||||
type: "irreversible_action",
|
||||
phase: "1",
|
||||
description: "Deploy to staging environment",
|
||||
context: "Phase 1 backend is complete, all tests pass",
|
||||
options: [
|
||||
{ id: "A", label: "Deploy to staging", description: "Deploy to staging for integration testing", recommended: true },
|
||||
{ id: "B", label: "Skip deployment", description: "Continue locally", recommended: false },
|
||||
{ id: "C", label: "Abort phase", description: "Await manual deployment", recommended: false },
|
||||
],
|
||||
default_option_id: "A",
|
||||
};
|
||||
|
||||
describe("escalate", () => {
|
||||
it("creates an escalation with a generated ID", () => {
|
||||
const escalation = protocol.escalate(baseInput);
|
||||
expect(escalation.id).toMatch(/^E-\d{3}$/);
|
||||
expect(escalation.type).toBe("irreversible_action");
|
||||
expect(escalation.description).toBe("Deploy to staging environment");
|
||||
expect(escalation.resolution).toBe("pending");
|
||||
});
|
||||
|
||||
it("increments escalation IDs sequentially", () => {
|
||||
const e1 = protocol.escalate(baseInput);
|
||||
const e2 = protocol.escalate(baseInput);
|
||||
expect(e1.id).toBe("E-001");
|
||||
expect(e2.id).toBe("E-002");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveEscalation", () => {
|
||||
it("resolves a pending escalation", () => {
|
||||
const escalation = protocol.escalate(baseInput);
|
||||
const resolved = protocol.resolveEscalation(escalation.id, "A");
|
||||
expect(resolved).not.toBeNull();
|
||||
expect(resolved!.resolution).toBe("approved");
|
||||
expect(resolved!.resolution_detail).toContain("A");
|
||||
});
|
||||
|
||||
it("returns null for unknown escalation ID", () => {
|
||||
const result = protocol.resolveEscalation("E-999", "A");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("supports different resolution types", () => {
|
||||
const escalation = protocol.escalate(baseInput);
|
||||
const rejected = protocol.resolveEscalation(escalation.id, "C", "rejected");
|
||||
expect(rejected!.resolution).toBe("rejected");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPendingEscalations", () => {
|
||||
it("returns pending escalations", () => {
|
||||
protocol.escalate(baseInput);
|
||||
protocol.escalate(baseInput);
|
||||
const pending = protocol.getPendingEscalations();
|
||||
expect(pending).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("returns empty list when no pending escalations", () => {
|
||||
expect(protocol.getPendingEscalations()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("removes resolved escalations from pending", () => {
|
||||
const e1 = protocol.escalate(baseInput);
|
||||
protocol.escalate(baseInput);
|
||||
protocol.resolveEscalation(e1.id, "A");
|
||||
expect(protocol.getPendingEscalations()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasPending", () => {
|
||||
it("returns true when there are pending escalations", () => {
|
||||
protocol.escalate(baseInput);
|
||||
expect(protocol.hasPending()).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when no pending escalations", () => {
|
||||
expect(protocol.hasPending()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatEscalation", () => {
|
||||
it("formats escalation for display", () => {
|
||||
const escalation = protocol.escalate(baseInput);
|
||||
const formatted = protocol.formatEscalation(escalation);
|
||||
expect(formatted).toContain("ESCALATION");
|
||||
expect(formatted).toContain("Irreversible Action");
|
||||
expect(formatted).toContain("Deploy to staging environment");
|
||||
expect(formatted).toContain("Options:");
|
||||
expect(formatted).toContain("recommended");
|
||||
expect(formatted).toContain("auto-proceed");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user