import { describe, expect, it, vi } from "vitest"; import { loadEnv } from "../src/config/env.js"; import { GiteaClient } from "../src/integrations/gitea/client.js"; import { assertSuccessfulActionRuns } from "../src/integrations/gitea/policy.js"; import type { GiteaActionRun, GiteaContent } from "../src/integrations/gitea/types.js"; import { decodeFile, MAX_MCP_READ_BYTES } from "../src/mcp/resultHelpers.js"; import { serializeAnalysisJob } from "../src/repositories/analysisJobRepository.js"; const actionRun = (input: { id: number; conclusion: string; workflow?: string; status?: string; headSha?: string; }): GiteaActionRun => ({ id: input.id, run_number: input.id, run_attempt: 1, display_title: input.workflow ?? "Managed validation / full", path: `.gitea/workflows/${input.workflow ?? "managed-validation.yml"}`, event: input.workflow ? "pull_request" : "workflow_dispatch", head_sha: input.headSha ?? "current-sha", head_branch: "chatgpt/feature", status: input.status ?? "completed", conclusion: input.conclusion, html_url: `https://gitea.example.com/runs/${input.id}`, jobs: input.workflow ? [] : [{ id: input.id * 10, html_url: `https://gitea.example.com/runs/${input.id}/jobs/${input.id * 10}`, run_id: input.id, name: "full", labels: [], run_attempt: 1, head_sha: input.headSha ?? "current-sha", head_branch: "chatgpt/feature", status: input.status ?? "completed", conclusion: input.conclusion, runner_id: 1, steps: [], created_at: "2026-10-06T21:33:06Z", }], }); const productionEnv = { NODE_ENV: "production", DATABASE_URL: "postgresql://projectbrain:strong-db-password@postgres:5432/projectbrain", PROJECTBRAIN_DATA_SOURCE: "database", PROJECTBRAIN_OWNER_USERNAME: "owner", PROJECTBRAIN_OWNER_PASSWORD: "correct-horse-battery-staple", PROJECTBRAIN_AUTH_SECRET: "production-secret-with-at-least-thirty-two-characters", } as const; describe("review remediation regressions", () => { it("authorizes a successful rerun of the same workflow and revision without discarding evidence", () => { const runs = [ actionRun({ id: 10, conclusion: "failure" }), actionRun({ id: 11, conclusion: "success" }), ]; expect(assertSuccessfulActionRuns(runs, "chatgpt/feature", "current-sha")).toEqual(runs); }); it("still fails closed when the latest run of any workflow is not successful", () => { expect(() => assertSuccessfulActionRuns([ actionRun({ id: 9, conclusion: "success" }), actionRun({ id: 10, conclusion: "success", workflow: "quality.yml" }), actionRun({ id: 11, conclusion: "failure", workflow: "security.yml" }), ], "chatgpt/feature", "current-sha")).toThrow("FEATURE_DELIVERY_CI_NOT_SUCCESSFUL"); expect(() => assertSuccessfulActionRuns([ actionRun({ id: 12, conclusion: "success" }), actionRun({ id: 13, conclusion: "", status: "in_progress" }), ], "chatgpt/feature", "current-sha")).toThrow("FEATURE_DELIVERY_FULL_VALIDATION_REQUIRED"); }); it("rejects shipped placeholder credentials in production", () => { expect(() => loadEnv({ ...productionEnv, PROJECTBRAIN_OWNER_PASSWORD: "replace-with-a-unique-password-of-at-least-12-characters", })).toThrow("PROJECTBRAIN_OWNER_PASSWORD must not use a placeholder"); expect(() => loadEnv({ ...productionEnv, PROJECTBRAIN_AUTH_SECRET: "replace-with-at-least-32-random-characters", })).toThrow("PROJECTBRAIN_AUTH_SECRET must not use a placeholder"); expect(() => loadEnv({ ...productionEnv, DATABASE_URL: "postgresql://projectbrain:projectbrain@postgres:5432/projectbrain", })).toThrow("DATABASE_URL database password must not use a placeholder"); }); it("bounds complete MCP repository file reads", () => { const file = (size: number): GiteaContent => ({ type: "file", name: "large.txt", path: "large.txt", sha: "abc", size, encoding: "base64", content: Buffer.from("safe content").toString("base64"), html_url: "https://gitea.example/large.txt", }); expect(decodeFile(file(MAX_MCP_READ_BYTES))).toBe("safe content"); expect(() => decodeFile(file(MAX_MCP_READ_BYTES + 1))).toThrow("MCP_FILE_READ_TOO_LARGE"); }); it("deletes the managed repository webhook through an exact bounded endpoint", async () => { const fetcher = vi.fn(async () => new Response(undefined, { status: 204 })); const client = new GiteaClient("https://gitea.example.com", "service-token", fetcher as typeof fetch); await client.deleteRepositoryWebhook("Acme", "Repo", 42); expect(String(fetcher.mock.calls[0][0])).toContain("/api/v1/repos/Acme/Repo/hooks/42"); expect(fetcher.mock.calls[0][1]?.method).toBe("DELETE"); }); it("treats partially successful analysis jobs as terminal but actionable", () => { const job = serializeAnalysisJob({ id: "job-partial", kind: "portfolio_health", status: "partial", actor: "owner", progressCurrent: 7, progressTotal: 10, summaryText: "7 repositories geanalyseerd, 3 mislukt.", summaryJson: { repositoryCount: 10, succeeded: 7, failed: 3, findingCount: 2 }, errorCode: "ANALYSIS_PARTIAL_FAILURE", cancelRequestedAt: null, queuedAt: new Date("2026-08-29T00:00:00Z"), startedAt: new Date("2026-08-29T00:00:01Z"), heartbeatAt: new Date("2026-08-29T00:01:00Z"), completedAt: new Date("2026-08-29T00:01:01Z"), expiresAt: new Date("2026-09-28T00:00:00Z"), createdAt: new Date("2026-08-29T00:00:00Z"), updatedAt: new Date("2026-08-29T00:01:01Z"), }); expect(job).toMatchObject({ status: "partial", terminal: true, pollAfterMs: 0, nextAction: "read-results-and-inspect-failures", }); }); });