4a58aa1657
- Type renames: CIConfig → CIAgentConfig, DEFAULT_CI_CONFIG → DEFAULT_CIAGENT_CONFIG - Type renames: CiMetadata → CIAgentMetadata, ParsedCiCommit → ParsedCIAgentCommit - Function renames: initCI → initCIAgent, isCIInitialized → isCIAgentInitialized - Function renames: extractCiBlock → extractCIAgentBlock, parseCiBlock → parseCIAgentBlock - Class renames: CiFiles → CIAgentFiles - Import paths: ci-files.js → ciagent-files.js - Directory paths: .ci/ → .ciagent/ across all source and test files - Check names: ".ci directory exists" → ".ciagent directory exists" - Check names: "CI config valid" → "CIAgent config valid" - Temp dir names: ci-*-test- → ciagent-*-test- - CLI examples: "ci init" → "ciagent init" - Fix deepMerge infinite recursion bug in config.ts - ---ci---/---/ci--- block markers preserved unchanged - All 31 test suites, 370 tests passing ---ci--- phase: 1 milestone: v0.5 plan: 07 task: 07-01-01 status: execute ---/ci---
119 lines
4.2 KiB
TypeScript
119 lines
4.2 KiB
TypeScript
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_CIAGENT_CONFIG } from "../types/config.js";
|
|
|
|
describe("EscalationProtocol", () => {
|
|
let tempDir: string;
|
|
let protocol: EscalationProtocol;
|
|
|
|
beforeEach(() => {
|
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-escalation-test-"));
|
|
const noAutoCommitConfig = {
|
|
...DEFAULT_CIAGENT_CONFIG,
|
|
git: { ...DEFAULT_CIAGENT_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");
|
|
});
|
|
});
|
|
}); |