import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { QualityOrchestratorService } from "./quality-orchestrator.service"; import { TokenBudgetService } from "../token-budget/token-budget.service"; import type { QualityGate, CompletionClaim, OrchestrationConfig, CompletionValidation, } from "./interfaces"; describe("QualityOrchestratorService", () => { let service: QualityOrchestratorService; const mockWorkspaceId = "workspace-1"; const mockTaskId = "task-1"; const mockAgentId = "agent-1"; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ QualityOrchestratorService, { provide: TokenBudgetService, useValue: { checkSuspiciousDoneClaim: vi.fn().mockResolvedValue({ suspicious: false }), }, }, ], }).compile(); service = module.get(QualityOrchestratorService); }); it("should be defined", () => { expect(service).toBeDefined(); }); describe("validateCompletion", () => { const claim: CompletionClaim = { taskId: mockTaskId, agentId: mockAgentId, workspaceId: mockWorkspaceId, claimedAt: new Date(), message: "Task completed successfully", filesChanged: ["src/test.ts"], }; const config: OrchestrationConfig = { workspaceId: mockWorkspaceId, gates: [ { id: "build", name: "Build Check", description: "Verify code compiles", type: "build", command: "echo 'build success'", required: true, order: 1, }, ], maxContinuations: 3, continuationBudget: 10000, strictMode: false, }; it("should accept completion when all required gates pass", async () => { const result = await service.validateCompletion(claim, config); expect(result.verdict).toBe("accepted"); expect(result.allGatesPassed).toBe(true); expect(result.requiredGatesFailed).toHaveLength(0); }); it("should reject completion when required gates fail", async () => { const failingConfig: OrchestrationConfig = { ...config, gates: [ { id: "build", name: "Build Check", description: "Verify code compiles", type: "build", command: "exit 1", required: true, order: 1, }, ], }; const result = await service.validateCompletion(claim, failingConfig); expect(result.verdict).toBe("rejected"); expect(result.allGatesPassed).toBe(false); expect(result.requiredGatesFailed).toContain("build"); }); it("should accept when optional gates fail but required gates pass", async () => { const mixedConfig: OrchestrationConfig = { ...config, gates: [ { id: "build", name: "Build Check", description: "Verify code compiles", type: "build", command: "echo 'success'", required: true, order: 1, }, { id: "coverage", name: "Coverage Check", description: "Check coverage", type: "coverage", command: "exit 1", required: false, order: 2, }, ], }; const result = await service.validateCompletion(claim, mixedConfig); expect(result.verdict).toBe("accepted"); expect(result.allGatesPassed).toBe(false); expect(result.requiredGatesFailed).toHaveLength(0); }); it("should provide feedback when gates fail", async () => { const failingConfig: OrchestrationConfig = { ...config, gates: [ { id: "test", name: "Test Suite", description: "Run tests", type: "test", command: "exit 1", required: true, order: 1, }, ], }; const result = await service.validateCompletion(claim, failingConfig); expect(result.feedback).toBeDefined(); expect(result.suggestedActions).toBeDefined(); expect(result.suggestedActions!.length).toBeGreaterThan(0); }); it("should run gates in order", async () => { const orderedConfig: OrchestrationConfig = { ...config, gates: [ { id: "gate3", name: "Third Gate", description: "Third", type: "custom", command: "echo 'third'", required: true, order: 3, }, { id: "gate1", name: "First Gate", description: "First", type: "custom", command: "echo 'first'", required: true, order: 1, }, { id: "gate2", name: "Second Gate", description: "Second", type: "custom", command: "echo 'second'", required: true, order: 2, }, ], }; const result = await service.validateCompletion(claim, orderedConfig); expect(result.gateResults[0].gateId).toBe("gate1"); expect(result.gateResults[1].gateId).toBe("gate2"); expect(result.gateResults[2].gateId).toBe("gate3"); }); }); describe("runGate", () => { it("should successfully run a gate with passing command", async () => { const gate: QualityGate = { id: "test-gate", name: "Test Gate", description: "Test description", type: "custom", command: "echo 'success'", required: true, order: 1, }; const result = await service.runGate(gate); expect(result.gateId).toBe("test-gate"); expect(result.gateName).toBe("Test Gate"); expect(result.passed).toBe(true); expect(result.duration).toBeGreaterThan(0); }); it("should fail a gate with failing command", async () => { const gate: QualityGate = { id: "fail-gate", name: "Failing Gate", description: "Should fail", type: "custom", command: "exit 1", required: true, order: 1, }; const result = await service.runGate(gate); expect(result.passed).toBe(false); expect(result.error).toBeDefined(); }); it("should capture output from gate execution", async () => { const gate: QualityGate = { id: "output-gate", name: "Output Gate", description: "Captures output", type: "custom", command: "echo 'test output'", required: true, order: 1, }; const result = await service.runGate(gate); expect(result.output).toContain("test output"); }); it("should validate expected output pattern", async () => { const gate: QualityGate = { id: "pattern-gate", name: "Pattern Gate", description: "Checks output pattern", type: "custom", command: "echo 'coverage: 90%'", expectedOutput: /coverage: \d+%/, required: true, order: 1, }; const result = await service.runGate(gate); expect(result.passed).toBe(true); }); it("should fail when expected output pattern does not match", async () => { const gate: QualityGate = { id: "bad-pattern-gate", name: "Bad Pattern Gate", description: "Pattern should not match", type: "custom", command: "echo 'no coverage info'", expectedOutput: /coverage: \d+%/, required: true, order: 1, }; const result = await service.runGate(gate); expect(result.passed).toBe(false); }); }); describe("shouldContinue", () => { const validation: CompletionValidation = { claim: { taskId: mockTaskId, agentId: mockAgentId, workspaceId: mockWorkspaceId, claimedAt: new Date(), message: "Done", filesChanged: [], }, gateResults: [], allGatesPassed: false, requiredGatesFailed: ["test"], verdict: "needs-continuation", }; const config: OrchestrationConfig = { workspaceId: mockWorkspaceId, gates: [], maxContinuations: 3, continuationBudget: 10000, strictMode: false, }; it("should continue when under max continuations", () => { const result = service.shouldContinue(validation, 1, config); expect(result).toBe(true); }); it("should not continue when at max continuations", () => { const result = service.shouldContinue(validation, 3, config); expect(result).toBe(false); }); it("should not continue when validation is accepted", () => { const acceptedValidation: CompletionValidation = { ...validation, verdict: "accepted", allGatesPassed: true, requiredGatesFailed: [], }; const result = service.shouldContinue(acceptedValidation, 1, config); expect(result).toBe(false); }); }); describe("generateContinuationPrompt", () => { it("should generate prompt with failed gate information", () => { const validation: CompletionValidation = { claim: { taskId: mockTaskId, agentId: mockAgentId, workspaceId: mockWorkspaceId, claimedAt: new Date(), message: "Done", filesChanged: [], }, gateResults: [ { gateId: "test", gateName: "Test Suite", passed: false, error: "Tests failed", duration: 1000, }, ], allGatesPassed: false, requiredGatesFailed: ["test"], verdict: "needs-continuation", }; const prompt = service.generateContinuationPrompt(validation); expect(prompt).toContain("Test Suite"); expect(prompt).toContain("failed"); }); it("should include suggested actions in prompt", () => { const validation: CompletionValidation = { claim: { taskId: mockTaskId, agentId: mockAgentId, workspaceId: mockWorkspaceId, claimedAt: new Date(), message: "Done", filesChanged: [], }, gateResults: [], allGatesPassed: false, requiredGatesFailed: ["lint"], verdict: "needs-continuation", suggestedActions: ["Run: pnpm lint --fix", "Check code style"], }; const prompt = service.generateContinuationPrompt(validation); expect(prompt).toContain("pnpm lint --fix"); expect(prompt).toContain("Check code style"); }); }); describe("generateRejectionFeedback", () => { it("should generate detailed rejection feedback", () => { const validation: CompletionValidation = { claim: { taskId: mockTaskId, agentId: mockAgentId, workspaceId: mockWorkspaceId, claimedAt: new Date(), message: "Done", filesChanged: [], }, gateResults: [ { gateId: "build", gateName: "Build Check", passed: false, error: "Compilation error", duration: 500, }, ], allGatesPassed: false, requiredGatesFailed: ["build"], verdict: "rejected", }; const feedback = service.generateRejectionFeedback(validation); expect(feedback).toContain("rejected"); expect(feedback).toContain("Build Check"); }); }); describe("getDefaultGates", () => { it("should return default gates for workspace", () => { const gates = service.getDefaultGates(mockWorkspaceId); expect(gates).toBeDefined(); expect(gates.length).toBeGreaterThan(0); expect(gates.some((g) => g.id === "build")).toBe(true); expect(gates.some((g) => g.id === "lint")).toBe(true); expect(gates.some((g) => g.id === "test")).toBe(true); }); it("should return gates in correct order", () => { const gates = service.getDefaultGates(mockWorkspaceId); for (let i = 1; i < gates.length; i++) { expect(gates[i].order).toBeGreaterThanOrEqual(gates[i - 1].order); } }); }); describe("recordContinuation", () => { it("should record continuation attempt", () => { const validation: CompletionValidation = { claim: { taskId: mockTaskId, agentId: mockAgentId, workspaceId: mockWorkspaceId, claimedAt: new Date(), message: "Done", filesChanged: [], }, gateResults: [], allGatesPassed: false, requiredGatesFailed: ["test"], verdict: "needs-continuation", }; expect(() => service.recordContinuation(mockTaskId, 1, validation)).not.toThrow(); }); it("should handle multiple continuation records", () => { const validation: CompletionValidation = { claim: { taskId: mockTaskId, agentId: mockAgentId, workspaceId: mockWorkspaceId, claimedAt: new Date(), message: "Done", filesChanged: [], }, gateResults: [], allGatesPassed: false, requiredGatesFailed: ["test"], verdict: "needs-continuation", }; service.recordContinuation(mockTaskId, 1, validation); service.recordContinuation(mockTaskId, 2, validation); expect(() => service.recordContinuation(mockTaskId, 3, validation)).not.toThrow(); }); }); });