import { beforeEach, describe, expect, it, vi } from "vitest"; import { ApprovalState, DeliveryStatus, DeliveryValidationStatus } from "@prisma/client"; const mocks = vi.hoisted(() => { const tx = { deliveryPlan: { create: vi.fn(), update: vi.fn() }, initiative: { update: vi.fn() }, outboxEvent: { create: vi.fn() }, deliveryRun: { create: vi.fn(), update: vi.fn() }, deliveryValidation: { create: vi.fn(), deleteMany: vi.fn() }, findingInvestigation: { findUnique: vi.fn(), update: vi.fn() }, repositoryFinding: { update: vi.fn() }, }; const prisma = { project: { findFirst: vi.fn() }, initiative: { findFirstOrThrow: vi.fn() }, deliveryPlan: { findFirst: vi.fn(), findMany: vi.fn() }, giteaRepositoryLink: { findFirst: vi.fn() }, deliveryRun: { findFirst: vi.fn() }, $transaction: vi.fn(async (callback: (client: typeof tx) => unknown) => callback(tx)), }; return { prisma, tx, createInitiativeForProject: vi.fn(), }; }); vi.mock("../src/db/prisma.js", () => ({ prisma: mocks.prisma })); vi.mock("../src/repositories/initiativesRepository.js", () => ({ createInitiativeForProject: mocks.createInitiativeForProject })); import { createFeatureDelivery, startFeatureDelivery, recordFeatureDeliveryActionValidation, reportedValidationResult, type FeatureDeliveryInput } from "../src/repositories/featureDeliveryRepository.js"; const baseInput = (riskLevel: FeatureDeliveryInput["riskLevel"]): FeatureDeliveryInput => ({ title: "Intent-first onboarding", intent: "Make onboarding understandable for nontechnical users.", priority: "normal", problem: "The current onboarding exposes too many technical concepts.", desiredOutcome: "A short intent-first onboarding flow.", users: ["new users"], goals: ["reduce cognitive load"], nonGoals: ["remove technical capabilities"], acceptanceCriteria: ["normal users can create a project without understanding MCP or Git internals"], implementationPlan: ["simplify the primary flow", "preserve technical drill-downs"], risks: ["navigation changes can affect deep links"], validationCommands: ["pnpm test"], riskLevel, }); const plan = (approvalState: ApprovalState, status: DeliveryStatus) => ({ id: "delivery-db-1", slug: "delivery-1", projectId: "project-db-1", initiativeId: "initiative-db-1", title: "Intent-first onboarding", intent: "Make onboarding understandable for nontechnical users.", priority: "normal", problem: "The current onboarding exposes too many technical concepts.", desiredOutcome: "A short intent-first onboarding flow.", usersJson: ["new users"], goalsJson: ["reduce cognitive load"], nonGoalsJson: [], acceptanceCriteriaJson: ["normal users understand the flow"], implementationPlanJson: ["simplify the primary flow"], risksJson: ["navigation changes"], validationCommandsJson: ["pnpm test"], status, approvalState, approvedAt: approvalState === ApprovalState.approved ? new Date("2026-08-31T20:00:00.000Z") : null, approvedBy: approvalState === ApprovalState.approved ? "chatgpt" : null, reviewFeedback: null, version: 1, createdBy: "chatgpt", updatedBy: "chatgpt", createdAt: new Date("2026-08-31T20:00:00.000Z"), updatedAt: new Date("2026-08-31T20:00:00.000Z"), initiative: null, runs: [], }); beforeEach(() => { vi.clearAllMocks(); mocks.prisma.project.findFirst.mockResolvedValue({ id: "project-db-1", slug: "projectbrain" }); mocks.createInitiativeForProject.mockResolvedValue({ id: "initiative-1" }); mocks.prisma.initiative.findFirstOrThrow.mockResolvedValue({ id: "initiative-db-1", slug: "initiative-1" }); mocks.tx.initiative.update.mockResolvedValue({}); mocks.tx.outboxEvent.create.mockResolvedValue({}); mocks.tx.findingInvestigation.findUnique.mockResolvedValue(null); }); describe("feature delivery risk authorization", () => { it.each([ ["low", "AUTO_ALLOWED"], ["medium", "AUTO_ALLOWED_WITH_AUDIT"], ] as const)("auto-authorizes %s reversible work", async (riskLevel, policyDecision) => { const created = plan(ApprovalState.approved, DeliveryStatus.approved); mocks.tx.deliveryPlan.create.mockResolvedValue(created); const result = await createFeatureDelivery("projectbrain", baseInput(riskLevel), "chatgpt"); expect(result.approvalState).toBe(ApprovalState.approved); expect(result.spec.status).toBe(DeliveryStatus.approved); expect(mocks.tx.deliveryPlan.create).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ approvalState: ApprovalState.approved, status: DeliveryStatus.approved, }), })); expect(mocks.tx.outboxEvent.create).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ eventType: "delivery.plan.auto_authorized", payloadJson: expect.objectContaining({ riskLevel, policyDecision }), }), })); }); it.each(["high", "destructive"] as const)("requires a human decision for %s work", async (riskLevel) => { const created = plan(ApprovalState.pending, DeliveryStatus.awaiting_approval); mocks.tx.deliveryPlan.create.mockResolvedValue(created); const result = await createFeatureDelivery("projectbrain", baseInput(riskLevel), "chatgpt"); expect(result.approvalState).toBe(ApprovalState.pending); expect(result.spec.status).toBe(DeliveryStatus.awaiting_approval); expect(mocks.tx.deliveryPlan.create).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ approvalState: ApprovalState.pending, status: DeliveryStatus.awaiting_approval, approvedAt: null, approvedBy: null, }), })); expect(mocks.tx.outboxEvent.create).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ eventType: "delivery.plan.user_decision_required", payloadJson: expect.objectContaining({ riskLevel, policyDecision: "USER_DECISION_REQUIRED" }), }), })); }); it("fails closed when a pending high-risk delivery tries to start repository execution", async () => { mocks.prisma.deliveryPlan.findFirst.mockResolvedValue(plan(ApprovalState.pending, DeliveryStatus.awaiting_approval)); await expect(startFeatureDelivery("projectbrain", "delivery-1", "chatgpt/high-risk-change")) .rejects.toThrow("FEATURE_DELIVERY_APPROVAL_REQUIRED"); expect(mocks.prisma.giteaRepositoryLink.findFirst).not.toHaveBeenCalled(); expect(mocks.tx.deliveryRun.create).not.toHaveBeenCalled(); }); }); describe("Actions evidence scope", () => { it("records only the observed workflow, preserving separately reported checks", async () => { const current = { ...plan(ApprovalState.approved, DeliveryStatus.validating), validationCommandsJson: ["dotnet test", "CI / build-test (pull_request)", "Live disposable provider smoke"], runs: [{ id: "run-1", validations: [], changes: [] }] }; mocks.prisma.deliveryPlan.findFirst.mockResolvedValue(current); mocks.tx.deliveryPlan.update.mockResolvedValue(current); await recordFeatureDeliveryActionValidation("projectbrain", "delivery-1", { provider: "gitea_actions", runId: 9205, runNumber: 500, url: "https://example.test/actions/runs/9205", workflowPath: "managed-validation.yml@refs/heads/chatgpt/work", headSha: "a".repeat(40), conclusion: "success", verifiedAt: "2026-09-08T00:00:00Z", }); expect(mocks.tx.deliveryValidation.create).toHaveBeenCalledTimes(1); expect(mocks.tx.deliveryValidation.create).toHaveBeenCalledWith({ data: expect.objectContaining({ command: "Gitea Actions: managed-validation.yml@refs/heads/chatgpt/work", status: "passed", headSha: "a".repeat(40), }) }); expect(mocks.tx.deliveryValidation.deleteMany).toHaveBeenCalledWith({ where: { deliveryRunId: "run-1", provider: "gitea_actions" } }); }); it.each(["CI / build-test (pull_request)", "Live provider smoke", "dotnet test"])("does not present legacy inferred %s as execution", (command) => { expect(reportedValidationResult({ command, status: DeliveryValidationStatus.passed, provider: "gitea_actions", summary: "managed-validation.yml" })) .toEqual({ command, status: "not_run", summary: expect.stringContaining("Legacy inferred result") }); }); it("preserves explicit reports and directly observed workflow results", () => { expect(reportedValidationResult({ command: "Live provider smoke", status: DeliveryValidationStatus.skipped, provider: null, summary: "Credentials unavailable" }).status).toBe("not_run"); expect(reportedValidationResult({ command: "Local contract suite", status: DeliveryValidationStatus.failed, provider: null, summary: "Failed" }).status).toBe("failed"); expect(reportedValidationResult({ command: "CI / build-test (pull_request)", status: DeliveryValidationStatus.failed, provider: "gitea_actions", summary: "ci.yml" }).status).toBe("failed"); expect(reportedValidationResult({ command: "Gitea Actions: ci.yml", status: DeliveryValidationStatus.passed, provider: "gitea_actions", summary: "ci.yml" }).status).toBe("passed"); }); });