Files
ci/src/core/commit-builder.test.ts
T
Jon Chery 4a58aa1657 refactor(rebrand): rename & rebrand CI → CIAgent across all source and test files
- 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---
2026-05-29 18:01:13 +00:00

373 lines
12 KiB
TypeScript

import { CommitBuilder } from "../core/commit-builder.js";
import { extractCIAgentBlock, parseCIAgentBlock } from "../core/commit-parser.js";
import { CIAgentMetadata } from "../types/commit-meta.js";
describe("CommitBuilder", () => {
describe("buildCiBlock", () => {
it("builds minimal ci block", () => {
const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute" };
const block = CommitBuilder.buildCiBlock(ci);
expect(block).toContain("phase: 1");
expect(block).toContain("milestone: v1.0");
expect(block).toContain("status: execute");
});
it("builds ci block with project", () => {
const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute", project: "task-api" };
const block = CommitBuilder.buildCiBlock(ci);
expect(block).toContain("project: task-api");
});
it("builds ci block without project when not set", () => {
const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute" };
const block = CommitBuilder.buildCiBlock(ci);
expect(block).not.toContain("project:");
});
it("builds ci block with decisions", () => {
const ci: CIAgentMetadata = {
phase: 1,
milestone: "v1.0",
status: "execute",
decisions: [
{
id: "D-001",
decision: "Use PostgreSQL",
rationale: "ACID compliance",
confidence: 0.9,
alternatives: ["MongoDB", "SQLite"],
},
],
};
const block = CommitBuilder.buildCiBlock(ci);
expect(block).toContain("decisions:");
expect(block).toContain("id: D-001");
expect(block).toContain("decision: Use PostgreSQL");
expect(block).toContain("alternatives: [MongoDB, SQLite]");
});
it("builds ci block with lessons", () => {
const ci: CIAgentMetadata = {
phase: 1,
milestone: "v1.0",
status: "complete",
lessons: ["Always use async bcrypt", "Check JWT expiry first"],
};
const block = CommitBuilder.buildCiBlock(ci);
expect(block).toContain("lessons:");
expect(block).toContain(" - Always use async bcrypt");
expect(block).toContain(" - Check JWT expiry first");
});
it("builds ci block with compound", () => {
const ci: CIAgentMetadata = {
phase: 1,
milestone: "v1.0",
status: "complete",
compound: {
category: "auth",
problem: "Token replay",
solution: "Refresh rotation",
},
};
const block = CommitBuilder.buildCiBlock(ci);
expect(block).toContain("compound:");
expect(block).toContain("category: auth");
expect(block).toContain("problem: Token replay");
expect(block).toContain("solution: Refresh rotation");
});
it("builds ci block with escalations", () => {
const ci: CIAgentMetadata = {
phase: 3,
milestone: "v1.0",
status: "execute",
escalations: [
{
id: "E-001",
type: "irreversible_action",
description: "Deploy to staging",
resolution: "pending",
},
],
};
const block = CommitBuilder.buildCiBlock(ci);
expect(block).toContain("escalations:");
expect(block).toContain("id: E-001");
expect(block).toContain("type: irreversible_action");
});
it("builds ci block with requirements", () => {
const ci: CIAgentMetadata = {
phase: 1,
milestone: "v1.0",
status: "complete",
requirements: {
covered: ["AUTH-01", "AUTH-02"],
partial: ["AUTH-03"],
},
};
const block = CommitBuilder.buildCiBlock(ci);
expect(block).toContain("requirements:");
expect(block).toContain("covered: [AUTH-01, AUTH-02]");
expect(block).toContain("partial: [AUTH-03]");
});
});
describe("round-trip: build then parse", () => {
it("round-trips a simple ci block", () => {
const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute" };
const block = CommitBuilder.buildCiBlock(ci);
const fullMessage = `feat(P01): test\n\n---ci---\n${block}\n---/ci---\n\nBody text`;
const extracted = extractCIAgentBlock(fullMessage)!;
const parsed = parseCIAgentBlock(extracted)!;
expect(parsed.phase).toBe(1);
expect(parsed.milestone).toBe("v1.0");
expect(parsed.status).toBe("execute");
});
it("round-trips decisions", () => {
const ci: CIAgentMetadata = {
phase: 1,
milestone: "v1.0",
status: "execute",
decisions: [
{
id: "D-001",
decision: "Use PostgreSQL",
rationale: "ACID compliance",
confidence: 0.9,
alternatives: ["MongoDB"],
},
],
};
const block = CommitBuilder.buildCiBlock(ci);
const fullMessage = `feat(P01): test\n\n---ci---\n${block}\n---/ci---`;
const extracted = extractCIAgentBlock(fullMessage)!;
const parsed = parseCIAgentBlock(extracted)!;
expect(parsed.decisions).toHaveLength(1);
expect(parsed.decisions![0].id).toBe("D-001");
expect(parsed.decisions![0].decision).toBe("Use PostgreSQL");
expect(parsed.decisions![0].confidence).toBe(0.9);
expect(parsed.decisions![0].alternatives).toEqual(["MongoDB"]);
});
it("round-trips compound with lessons", () => {
const ci: CIAgentMetadata = {
phase: 2,
milestone: "v1.0",
status: "complete",
compound: {
category: "auth",
problem: "Token replay attacks",
solution: "Refresh rotation with family IDs",
},
lessons: ["Token rotation is not optional"],
};
const block = CommitBuilder.buildCiBlock(ci);
const fullMessage = `compound(P02): test\n\n---ci---\n${block}\n---/ci---`;
const extracted = extractCIAgentBlock(fullMessage)!;
const parsed = parseCIAgentBlock(extracted)!;
expect(parsed.compound!.category).toBe("auth");
expect(parsed.compound!.problem).toBe("Token replay attacks");
expect(parsed.lessons).toHaveLength(1);
});
it("round-trips project field", () => {
const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute", project: "task-api" };
const block = CommitBuilder.buildCiBlock(ci);
const fullMessage = `feat(task-api/P01): test\n\n---ci---\n${block}\n---/ci---`;
const extracted = extractCIAgentBlock(fullMessage)!;
const parsed = parseCIAgentBlock(extracted)!;
expect(parsed.project).toBe("task-api");
});
});
describe("buildInitCommit", () => {
it("builds an init commit message", () => {
const msg = CommitBuilder.buildInitCommit({
projectName: "task-api",
phaseCount: 4,
milestone: "v1.0",
specification: "Build a REST API for task management",
requirements: ["AUTH-01", "TASK-01"],
constraints: ["Node.js"],
outOfScope: ["Admin dashboard"],
});
expect(msg).toContain("docs(init):");
expect(msg).toContain("---ci---");
expect(msg).toContain("phase: 0");
expect(msg).toContain("milestone: v1.0");
expect(msg).toContain("Build a REST API for task management");
expect(msg).toContain("AUTH-01");
});
it("builds an init commit message with project", () => {
const msg = CommitBuilder.buildInitCommit({
projectName: "task-api",
phaseCount: 4,
milestone: "v1.0",
project: "task-api",
specification: "Build a REST API",
requirements: ["AUTH-01"],
});
expect(msg).toContain("project: task-api");
});
});
describe("buildTaskCommit", () => {
it("builds a task commit message", () => {
const msg = CommitBuilder.buildTaskCommit({
type: "feat",
phase: 1,
milestone: "v1.0",
plan: "01-01",
task: "01-01-02",
subject: "create user registration endpoint",
status: "execute",
decisions: [
{
id: "D-003",
decision: "Use bcrypt with 12 rounds",
rationale: "Industry standard",
confidence: 0.88,
alternatives: ["argon2"],
},
],
requirements: { covered: ["AUTH-01"], partial: [] },
});
expect(msg).toContain("feat(P01-01-02):");
expect(msg).toContain("plan: 01-01");
expect(msg).toContain("task: 01-01-02");
expect(msg).toContain("D-003");
expect(msg).toContain("AUTH-01");
});
it("builds a task commit with project prefix", () => {
const msg = CommitBuilder.buildTaskCommit({
type: "feat",
phase: 1,
milestone: "v1.0",
project: "task-api",
plan: "01-01",
task: "01-01-02",
subject: "registration endpoint",
status: "execute",
});
expect(msg).toContain("feat(task-api/P01-01-02):");
expect(msg).toContain("project: task-api");
});
});
describe("buildPhaseCompletionCommit", () => {
it("builds a phase completion commit", () => {
const msg = CommitBuilder.buildPhaseCompletionCommit({
phase: 1,
milestone: "v1.0",
phaseName: "authentication",
tasksCompleted: 4,
tasksTotal: 4,
taskNames: ["scaffold", "registration", "login", "reset"],
requirements: { covered: ["AUTH-01", "AUTH-02", "AUTH-03", "AUTH-04"], partial: [] },
lessons: ["Always use async bcrypt"],
});
expect(msg).toContain("docs(P01): complete authentication phase");
expect(msg).toContain("status: complete");
expect(msg).toContain("Tasks completed: 4/4");
expect(msg).toContain("Always use async bcrypt");
});
});
describe("buildCompoundCommit", () => {
it("builds a compound commit", () => {
const msg = CommitBuilder.buildCompoundCommit({
phase: 1,
milestone: "v1.0",
category: "auth",
problem: "Token replay allows persistent access",
solution: "Refresh token rotation with family IDs",
lessons: ["Rotation is not optional"],
});
expect(msg).toContain("compound(P01):");
expect(msg).toContain("category: auth");
expect(msg).toContain("problem: Token replay");
expect(msg).toContain("solution: Refresh token rotation");
});
});
describe("buildDecisionCommit", () => {
it("builds a decision-only commit", () => {
const msg = CommitBuilder.buildDecisionCommit({
phase: 1,
milestone: "v1.0",
subject: "use PostgreSQL over MongoDB",
decisions: [
{
id: "D-001",
decision: "PostgreSQL",
rationale: "ACID",
confidence: 0.92,
alternatives: ["MongoDB"],
},
],
});
expect(msg).toContain("decision(P01): use PostgreSQL over MongoDB");
expect(msg).toContain("D-001");
});
});
describe("buildEscalationCommit", () => {
it("builds an escalation commit", () => {
const msg = CommitBuilder.buildEscalationCommit({
phase: 3,
milestone: "v1.0",
subject: "deploy to staging requires approval",
escalations: [
{
id: "E-001",
type: "irreversible_action",
description: "Deploy to staging",
resolution: "pending",
},
],
});
expect(msg).toContain("escalation(P03): deploy to staging requires approval");
expect(msg).toContain("E-001");
});
});
describe("buildVerifyCommit", () => {
it("builds a verify commit", () => {
const msg = CommitBuilder.buildVerifyCommit({
phase: 1,
milestone: "v1.0",
subject: "all must-haves pass automated tests",
requirements: { covered: ["AUTH-01", "AUTH-02"], partial: [] },
});
expect(msg).toContain("verify(P01): all must-haves pass automated tests");
expect(msg).toContain("AUTH-01");
});
});
});