import { describe, expect, it, vi } from "vitest"; import { loadEnv } from "../src/config/env.js"; import { GiteaClient } from "../src/integrations/gitea/client.js"; import type { LinkedRepository } from "../src/integrations/gitea/service.js"; import type { GiteaActionRun, GiteaPullRequest } from "../src/integrations/gitea/types.js"; import { analyzeLinkedRepository, classifyActionFindings, loadActiveWorkflowEvents, parseWorkflowTriggerEvents } from "../src/services/repositoryAnalysis.js"; import { projectAnalysisItemScope, resolveAnalysisTargets } from "../src/repositories/analysisJobRepository.js"; const envInput = { NODE_ENV: "test", DATABASE_URL: "postgresql://example.invalid/projectbrain", PROJECTBRAIN_DATA_SOURCE: "database", PROJECTBRAIN_OWNER_USERNAME: "owner", PROJECTBRAIN_OWNER_PASSWORD: "correct-horse-battery-staple", PROJECTBRAIN_AUTH_SECRET: "test-secret-with-at-least-thirty-two-characters", PROJECTBRAIN_GITEA_URL: "https://gitea.example.com", PROJECTBRAIN_GITEA_TOKEN: "a-token-with-at-least-twenty-characters", } satisfies NodeJS.ProcessEnv; const env = loadEnv(envInput); const link: LinkedRepository = { id: "repo-1", projectId: "project-1", projectSlug: "project-one", projectName: "Project One", owner: "Acme", repository: "Repo", fullName: "Acme/Repo", defaultBranch: "main", accessMode: "read_write", role: "primary", isPrimary: true, syncStatus: "ready", }; const actionRun = (input: Partial & Pick): GiteaActionRun => ({ display_title: "CI", path: "ci.yml@refs/heads/main", event: "push", head_sha: `sha-${input.run_number}`, head_branch: "main", status: "completed", conclusion: "success", html_url: `https://gitea.example/actions/runs/${input.id}`, completed_at: `2026-08-28T00:${String(input.run_number).padStart(2, "0")}:00Z`, ...input, }); const pullRequest = (number: number, branch: string): GiteaPullRequest => ({ id: number, number, title: `PR ${number}`, state: "open", html_url: `https://gitea.example/pulls/${number}`, merged: false, created_at: "2026-08-28T00:00:00Z", updated_at: "2026-08-28T00:00:00Z", head: { ref: branch, sha: `head-${number}` }, base: { ref: "main", sha: "main-sha" }, }); describe("actionable Gitea Actions classification", () => { it.each([ { includeBranches: false, includePullRequests: true, includeActions: true, absent: ["REPOSITORY_EMPTY", "DEFAULT_BRANCH_UNPROTECTED", "BRANCH_SPRAWL"] }, { includeBranches: true, includePullRequests: false, includeActions: true, absent: ["OPEN_PR_BACKLOG", "TOPIC_BRANCH_ACTION_FAILED"] }, { includeBranches: true, includePullRequests: true, includeActions: false, absent: ["DEFAULT_BRANCH_ACTION_FAILED", "SCHEDULED_ACTION_FAILED", "TOPIC_BRANCH_ACTION_FAILED"] }, { includeBranches: true, includePullRequests: true, includeActions: true, absent: [] }, ])("records only actual rule coverage for partial analysis %j", async ({ absent, ...request }) => { const client = new GiteaClient("https://gitea.example", "fixture-token"); vi.spyOn(client, "getRepository").mockResolvedValue({ default_branch: "main", archived: false, private: true, permissions: { push: true } } as never); const branches = vi.spyOn(client, "listBranches").mockResolvedValue([{ name: "main", protected: true }] as never); const prs = vi.spyOn(client, "listPullRequests").mockResolvedValue([]); const actions = vi.spyOn(client, "listActionRuns").mockResolvedValue({ workflow_runs: [actionRun({ id: 1, run_number: 1 })] } as never); vi.spyOn(client, "getContents").mockResolvedValue([]); const detail = await analyzeLinkedRepository(env, link, request, client); for (const code of absent) expect(detail.evaluatedRuleCodes).not.toContain(code); expect(branches).toHaveBeenCalledTimes(request.includeBranches ? 1 : 0); expect(prs).toHaveBeenCalledTimes(request.includePullRequests ? 1 : 0); expect(actions).toHaveBeenCalledTimes(request.includeActions ? 1 : 0); if (!absent.length) expect(detail.evaluatedRuleCodes).toEqual(expect.arrayContaining([ "DEFAULT_BRANCH_UNPROTECTED", "OPEN_PR_BACKLOG", "DEFAULT_BRANCH_ACTION_FAILED", "TOPIC_BRANCH_ACTION_FAILED", ])); }); it("does not revive an older failure that finished after a newer successful run", () => { const findings = classifyActionFindings({ defaultBranch: "main", recentRuns: [], openPullRequests: [], defaultBranchRuns: [ actionRun({ id: 1, run_number: 1, conclusion: "failure", completed_at: "2026-09-09T12:00:00Z" }), actionRun({ id: 2, run_number: 2, conclusion: "success", completed_at: "2026-09-09T11:00:00Z" }), ] }); expect(findings).toEqual([]); }); it("retains a newer failure even if an older successful run finishes later", () => { const findings = classifyActionFindings({ defaultBranch: "main", recentRuns: [], openPullRequests: [], defaultBranchRuns: [ actionRun({ id: 1, run_number: 1, conclusion: "success", completed_at: "2026-09-09T12:00:00Z" }), actionRun({ id: 2, run_number: 2, conclusion: "failure", completed_at: "2026-09-09T11:00:00Z" }), ] }); expect(findings).toHaveLength(1); expect(findings[0]).toMatchObject({ code: "DEFAULT_BRANCH_ACTION_FAILED", evidence: { runId: 2 } }); }); it("does not hide a failing schedule behind a successful push in the same workflow", () => { const findings = classifyActionFindings({ defaultBranch: "main", recentRuns: [], openPullRequests: [], defaultBranchRuns: [ actionRun({ id: 1, run_number: 1, event: "schedule", conclusion: "failure" }), actionRun({ id: 2, run_number: 2, event: "push", conclusion: "success" }), ] }); expect(findings).toHaveLength(1); expect(findings[0]).toMatchObject({ code: "SCHEDULED_ACTION_FAILED", severity: "warning", evidence: { consecutiveFailures: 1 } }); }); it("does not count push failures toward the scheduled failure threshold", () => { const findings = classifyActionFindings({ defaultBranch: "main", recentRuns: [], openPullRequests: [], defaultBranchRuns: [ actionRun({ id: 1, run_number: 1, conclusion: "failure" }), actionRun({ id: 2, run_number: 2, conclusion: "failure" }), actionRun({ id: 3, run_number: 3, event: "schedule", conclusion: "failure" }), ] }); expect(findings).toHaveLength(2); expect(findings.find((finding) => finding.code === "SCHEDULED_ACTION_FAILED")).toMatchObject({ severity: "warning", evidence: { consecutiveFailures: 1 } }); expect(findings.find((finding) => finding.code === "DEFAULT_BRANCH_ACTION_FAILED")).toMatchObject({ severity: "critical", evidence: { consecutiveFailures: 2 } }); }); it.each(["pull_request", "pull_request_target", "push"])("treats an explicit PR ref as PR evidence even with head_branch main (%s)", (event) => { const run = actionRun({ id: 2, run_number: 2, event, path: "ci.yml@refs/pull/7/merge", head_branch: "main", conclusion: "failure" }); const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [actionRun({ id: 1, run_number: 1 }), run], recentRuns: [run], openPullRequests: [pullRequest(7, "codex/work")] }); expect(findings).toHaveLength(1); expect(findings[0]).toMatchObject({ code: "TOPIC_BRANCH_ACTION_FAILED", severity: "info", evidence: { branch: "codex/work" } }); }); it("ignores in-flight agent branches while retaining a real default-branch failure", () => { const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [actionRun({ id: 1, run_number: 1, conclusion: "failure" })], recentRuns: [ actionRun({ id: 2, run_number: 2, head_branch: "codex/work", path: "ci.yml@refs/heads/codex/work", conclusion: "failure" }), actionRun({ id: 1, run_number: 1, conclusion: "failure" }), ], openPullRequests: [pullRequest(2, "codex/work")], }); expect(findings).toHaveLength(1); expect(findings[0]).toMatchObject({ code: "DEFAULT_BRANCH_ACTION_FAILED", severity: "critical", subject: "default:ci.yml" }); }); it("escalates a scheduled workflow only after three consecutive failures", () => { const scheduled = (count: number) => classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: Array.from({ length: count }, (_, index) => actionRun({ id: 10 - index, run_number: 10 - index, event: "schedule", path: "canary.yml@refs/heads/main", conclusion: "failure", })), recentRuns: [], openPullRequests: [], })[0]; expect(scheduled(2)).toMatchObject({ code: "SCHEDULED_ACTION_FAILED", severity: "warning", evidence: { consecutiveFailures: 2 } }); expect(scheduled(3)).toMatchObject({ code: "SCHEDULED_ACTION_FAILED", severity: "critical", evidence: { consecutiveFailures: 3 } }); }); it("keeps the previous completed failure while a newer run is still in progress", () => { const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [ actionRun({ id: 3, run_number: 3, status: "in_progress", conclusion: undefined }), actionRun({ id: 2, run_number: 2, conclusion: "failure" }), ], recentRuns: [], openPullRequests: [], }); expect(findings[0]).toMatchObject({ code: "DEFAULT_BRANCH_ACTION_FAILED", evidence: { runId: 2 } }); }); it("does not treat cancelled or skipped conclusions as failures", () => { const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [actionRun({ id: 2, run_number: 2, conclusion: "cancelled" }), actionRun({ id: 1, run_number: 1, conclusion: "failure" })], recentRuns: [], openPullRequests: [], }); expect(findings).toEqual([]); }); it("defensively rejects non-default runs even if Gitea ignores the branch query", () => { const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [ actionRun({ id: 3, run_number: 3, head_branch: "codex/work", path: "ci.yml@refs/heads/codex/work", conclusion: "failure" }), actionRun({ id: 2, run_number: 2, head_branch: "main", conclusion: "success" }), ], recentRuns: [], openPullRequests: [], }); expect(findings).toEqual([]); }); it("keeps workflows independent and includes actionable run evidence", () => { const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [ actionRun({ id: 5, run_number: 5, path: "build.yml@refs/heads/main", conclusion: "success" }), actionRun({ id: 4, run_number: 4, path: "deploy.yml@refs/heads/main", conclusion: "failure", html_url: "https://gitea.example/actions/runs/4" }), ], recentRuns: [], openPullRequests: [], }); expect(findings).toHaveLength(1); expect(findings[0]).toMatchObject({ code: "DEFAULT_BRANCH_ACTION_FAILED", subject: "default:deploy.yml", evidence: { workflow: "deploy.yml", branch: "main", runUrl: "https://gitea.example/actions/runs/4" }, }); }); it("ignores historical failures for workflow files removed from the default branch", () => { const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [ actionRun({ id: 5, run_number: 5, path: "managed-validation.yml@refs/heads/main", conclusion: "success" }), actionRun({ id: 4, run_number: 4, path: "ci.yml@refs/heads/main", conclusion: "failure" }), ], recentRuns: [], openPullRequests: [], activeWorkflows: ["managed-validation.yml"], }); expect(findings).toEqual([]); }); it("retains failures for workflow files that still exist on the default branch", () => { const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [actionRun({ id: 4, run_number: 4, path: "ci.yml@refs/heads/main", conclusion: "failure" })], recentRuns: [], openPullRequests: [], activeWorkflows: ["ci.yml"], }); expect(findings[0]).toMatchObject({ code: "DEFAULT_BRANCH_ACTION_FAILED", subject: "default:ci.yml" }); }); it("ignores a historical event removed from a workflow while retaining current events", () => { const historicalPush = actionRun({ id: 11, run_number: 11, path: "unraid-deploy.yml@refs/heads/main", event: "push", conclusion: "failure" }); const manualFailure = actionRun({ id: 12, run_number: 12, path: "unraid-deploy.yml@refs/heads/main", event: "workflow_dispatch", conclusion: "failure" }); const currentEvents = new Map([["unraid-deploy.yml", new Set(["workflow_dispatch"])]]); const obsolete = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [historicalPush], recentRuns: [], openPullRequests: [], activeWorkflowEvents: currentEvents }); expect(obsolete.some((finding) => finding.code === "DEFAULT_BRANCH_ACTION_FAILED")).toBe(false); const current = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [manualFailure, historicalPush], recentRuns: [], openPullRequests: [], activeWorkflowEvents: currentEvents }); expect(current[0]).toMatchObject({ code: "DEFAULT_BRANCH_ACTION_FAILED", subject: "default:unraid-deploy.yml", evidence: { event: "workflow_dispatch", runId: 12 } }); }); it("keeps historical failures actionable when current workflow events are unknown", () => { const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [actionRun({ id: 4, run_number: 4, path: "ci.yml@refs/heads/main", conclusion: "failure" })], recentRuns: [], openPullRequests: [], activeWorkflowEvents: new Map([["ci.yml", undefined]]), }); expect(findings[0]).toMatchObject({ code: "DEFAULT_BRANCH_ACTION_FAILED", subject: "default:ci.yml" }); }); it("parses common top-level workflow trigger forms and rejects ambiguous syntax", () => { expect([...parseWorkflowTriggerEvents("on: push\njobs: {}") ?? []]).toEqual(["push"]); expect([...parseWorkflowTriggerEvents("on: [push, pull_request]\njobs: {}") ?? []]).toEqual(["push", "pull_request"]); expect([...parseWorkflowTriggerEvents("on:\n push:\n branches: [main]\n workflow_dispatch:\njobs: {}") ?? []]).toEqual(["push", "workflow_dispatch"]); expect(parseWorkflowTriggerEvents("on: { push: null }\njobs: {}"), "inline maps stay conservative").toBeUndefined(); expect(parseWorkflowTriggerEvents("jobs:\n build:\n runs-on: ubuntu-latest"), "missing on stays conservative").toBeUndefined(); }); it("loads current workflow events and falls back conservatively when one file cannot be read", async () => { const fetcher = vi.fn(async (input: string | URL | Request) => { const url = String(input); if (url.includes("manual.yml")) return new Response(JSON.stringify({ type: "file", name: "manual.yml", path: ".gitea/workflows/manual.yml", sha: "manual", size: 20, encoding: "base64", content: Buffer.from("on:\n workflow_dispatch:\n").toString("base64") }), { status: 200 }); return new Response(JSON.stringify({ message: "unavailable" }), { status: 503 }); }); const client = new GiteaClient("https://gitea.example.com", "secret-token", fetcher as typeof fetch); const events = await loadActiveWorkflowEvents(client, "Acme", "Repo", "main", [ { type: "file", name: "manual.yml", path: ".gitea/workflows/manual.yml", sha: "manual", size: 20 }, { type: "file", name: "unknown.yml", path: ".gitea/workflows/unknown.yml", sha: "unknown", size: 20 }, ]); expect([...(events?.get("manual.yml") ?? [])]).toEqual(["workflow_dispatch"]); expect(events?.has("unknown.yml")).toBe(true); expect(events?.get("unknown.yml")).toBeUndefined(); }); it("downgrades an open pull-request failure to info and ignores closed pull requests", () => { const run = actionRun({ id: 8, run_number: 8, event: "pull_request", head_branch: undefined, path: "release-gates.yml@refs/pull/7/head", conclusion: "failure", }); const active = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [actionRun({ id: 1, run_number: 1 })], recentRuns: [run], openPullRequests: [pullRequest(7, "feature/release")] }); const closed = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [actionRun({ id: 1, run_number: 1 })], recentRuns: [run], openPullRequests: [] }); expect(active).toHaveLength(1); expect(active[0]).toMatchObject({ code: "TOPIC_BRANCH_ACTION_FAILED", severity: "info", evidence: { branch: "feature/release" } }); expect(closed).toEqual([]); }); it("recognizes Gitea pull-request merge refs", () => { const findings = classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [actionRun({ id: 1, run_number: 1 })], recentRuns: [actionRun({ id: 9, run_number: 9, head_branch: undefined, event: "pull_request", path: "ci.yml@refs/pull/9/merge", conclusion: "failure" })], openPullRequests: [pullRequest(9, "feature/merge-ref")], }); expect(findings[0]).toMatchObject({ code: "TOPIC_BRANCH_ACTION_FAILED", evidence: { branch: "feature/merge-ref" } }); }); it("reports absent default-branch CI and an agent branch configured as default separately", () => { expect(classifyActionFindings({ defaultBranch: "main", defaultBranchRuns: [], recentRuns: [], openPullRequests: [] })[0]).toMatchObject({ code: "DEFAULT_BRANCH_CI_ABSENT", severity: "warning" }); expect(classifyActionFindings({ defaultBranch: "codex/temporary", defaultBranchRuns: [], recentRuns: [], openPullRequests: [] })).toEqual([ expect.objectContaining({ code: "DEFAULT_BRANCH_IS_AGENT_BRANCH", severity: "warning", subject: "codex/temporary" }), ]); }); it("reclassifies the frozen twelve-repository production sample without hiding failures", () => { const failed = (branch: string, event: string = "push", path = "ci.yml") => actionRun({ id: 10, run_number: 10, head_branch: branch, event, path: `${path}@refs/heads/${branch}`, conclusion: "failure" }); const cases = [ { repository: "Gifted", defaultBranch: "main", defaults: [failed("main")], recent: [] }, { repository: "ITWorx-Pulse", defaultBranch: "master", defaults: [failed("master")], recent: [failed("codex/integration")] }, { repository: "Microsoft-Cloud-Operations-Platform", defaultBranch: "main", defaults: [failed("main", "push", "unraid-deploy.yml")], recent: [] }, { repository: "MobilityOps", defaultBranch: "master", defaults: [failed("master", "schedule", "live-canary.yml"), actionRun({ ...failed("master", "schedule", "live-canary.yml"), id: 9, run_number: 9 }), actionRun({ ...failed("master", "schedule", "live-canary.yml"), id: 8, run_number: 8 })], recent: [] }, { repository: "PentestForge", defaultBranch: "main", defaults: [], recent: [failed("chatgpt/m1-control-plane", "workflow_dispatch", "managed-validation.yml")] }, { repository: "Portfolio", defaultBranch: "main", defaults: [failed("main", "push", "deploy.yml")], recent: [] }, { repository: "RAGcore", defaultBranch: "main", defaults: [failed("main", "push", "unraid-deploy.yml")], recent: [] }, { repository: "chimera-retroarch", defaultBranch: "codex/ps5-loader-entry-diagnosis", defaults: [failed("codex/ps5-loader-entry-diagnosis", "schedule", "crowdin-daily.yml")], recent: [] }, { repository: "geointel", defaultBranch: "main", defaults: [], recent: [actionRun({ id: 7, run_number: 7, head_branch: undefined, event: "pull_request", path: "release-gates.yml@refs/pull/1/head", conclusion: "failure" })], pullRequests: [pullRequest(1, "feature/geointel")] }, { repository: "itworx-knowledge-hub", defaultBranch: "main", defaults: [failed("main")], recent: [] }, { repository: "opscenter", defaultBranch: "main", defaults: [actionRun({ id: 6, run_number: 6, head_branch: "main", conclusion: "success" })], recent: [failed("chatgpt/remediation")] }, { repository: "spoortrack", defaultBranch: "main", defaults: [], recent: [actionRun({ id: 5, run_number: 5, head_branch: undefined, event: "pull_request", path: "ci.yml@refs/pull/1/head", conclusion: "failure" })], pullRequests: [pullRequest(1, "feature/spoortrack")] }, ]; const classified = cases.map((entry) => ({ repository: entry.repository, findings: classifyActionFindings({ defaultBranch: entry.defaultBranch, defaultBranchRuns: entry.defaults, recentRuns: entry.recent, openPullRequests: entry.pullRequests ?? [] }), })); expect(classified.filter((entry) => entry.findings.some((finding) => finding.severity === "critical")).map((entry) => entry.repository)).toEqual([ "Gifted", "ITWorx-Pulse", "Microsoft-Cloud-Operations-Platform", "MobilityOps", "Portfolio", "RAGcore", "itworx-knowledge-hub", ]); expect(classified.find((entry) => entry.repository === "opscenter")?.findings).toEqual([]); expect(classified.find((entry) => entry.repository === "PentestForge")?.findings.map((finding) => finding.code)).toEqual(["DEFAULT_BRANCH_CI_ABSENT"]); expect(classified.find((entry) => entry.repository === "chimera-retroarch")?.findings.map((finding) => finding.code)).toEqual(["DEFAULT_BRANCH_IS_AGENT_BRANCH"]); expect(classified.find((entry) => entry.repository === "geointel")?.findings.map((finding) => finding.code)).toEqual(["DEFAULT_BRANCH_CI_ABSENT", "TOPIC_BRANCH_ACTION_FAILED"]); expect(classified.find((entry) => entry.repository === "spoortrack")?.findings.map((finding) => finding.code)).toEqual(["DEFAULT_BRANCH_CI_ABSENT", "TOPIC_BRANCH_ACTION_FAILED"]); }); }); describe("bounded repository analysis", () => { it("matches project-scoped jobs by internal id or browser-facing slug", () => { expect(projectAnalysisItemScope("web-privacy-auditor")).toEqual({ OR: [{ projectId: "web-privacy-auditor" }, { projectSlug: "web-privacy-auditor" }], }); }); it("discovers unlinked current and future repositories for an unscoped portfolio job", () => { const repositories = [ { id: 1, name: "Repo", full_name: "Acme/Repo", private: true, archived: false, default_branch: "main", html_url: "https://gitea.example/Acme/Repo", updated_at: "2026-08-26T00:00:00Z", permissions: { push: true }, owner: { login: "Acme" } }, { id: 2, name: "Future", full_name: "Acme/Future", private: true, archived: false, default_branch: "trunk", html_url: "https://gitea.example/Acme/Future", updated_at: "2026-08-26T00:00:00Z", permissions: { pull: true }, owner: { login: "Acme" } }, ]; const targets = resolveAnalysisTargets([link], repositories, { includeBranches: true, includePullRequests: true, includeActions: true }); expect(targets.map((target) => target.fullName)).toEqual(["Acme/Future", "Acme/Repo"]); expect(targets.find((target) => target.fullName === "Acme/Repo")?.repositoryLinkId).toBe("repo-1"); expect(targets.find((target) => target.fullName === "Acme/Future")).toMatchObject({ repositoryLinkId: undefined, projectSlug: "gitea-all", accessMode: "read_only" }); }); it("keeps project-scoped jobs limited to explicit ProjectBrain links", () => { const targets = resolveAnalysisTargets([link], [], { projectIds: ["project-one"], includeBranches: true, includePullRequests: true, includeActions: true }); expect(targets).toHaveLength(1); expect(targets[0].repositoryLinkId).toBe("repo-1"); }); it("reduces several Gitea reads to a compact finding summary", async () => { const branches = Array.from({ length: 26 }, (_, index) => ({ name: index === 0 ? "main" : `branch-${index}`, commit: { id: `sha-${index}` }, protected: false })); const pullRequests = Array.from({ length: 11 }, (_, index) => ({ id: index, number: index, title: `PR ${index}`, state: "open", html_url: `https://gitea.example/pr/${index}`, merged: false, created_at: "2026-08-26T00:00:00Z", updated_at: "2026-08-26T00:00:00Z", head: { ref: `branch-${index}`, sha: `sha-${index}` }, base: { ref: "main", sha: "main-sha" } })); const fetcher = vi.fn(async (input: string | URL | Request) => { const url = String(input); if (url.includes("/actions/runs")) return new Response(JSON.stringify({ total_count: 1, workflow_runs: [{ id: 1, run_number: 1, display_title: "CI", path: "ci.yml", event: "push", head_sha: "sha", head_branch: "main", status: "completed", conclusion: "failure", html_url: "https://gitea.example/run/1" }] }), { status: 200 }); if (url.includes("/contents/.gitea/workflows/ci.yml")) return new Response(JSON.stringify({ type: "file", name: "ci.yml", path: ".gitea/workflows/ci.yml", sha: "workflow-file-sha", size: 20, encoding: "base64", content: Buffer.from("on:\n push:\n").toString("base64") }), { status: 200 }); if (url.includes("/contents/.gitea/workflows")) return new Response(JSON.stringify([{ type: "file", name: "ci.yml", path: ".gitea/workflows/ci.yml", sha: "workflow-sha", size: 1 }]), { status: 200 }); if (url.includes("/branches")) return new Response(JSON.stringify(branches), { status: 200 }); if (url.includes("/pulls")) return new Response(JSON.stringify(pullRequests), { status: 200 }); return new Response(JSON.stringify({ id: 1, name: "Repo", full_name: "Acme/Repo", private: true, archived: false, default_branch: "main", html_url: "https://gitea.example/Acme/Repo", updated_at: "2026-08-26T00:00:00Z", permissions: { push: true }, owner: { login: "Acme" } }), { status: 200 }); }); const detail = await analyzeLinkedRepository(env, link, { includeBranches: true, includePullRequests: true, includeActions: true }, new GiteaClient("https://gitea.example.com", "secret-token", fetcher as typeof fetch)); expect(detail.branchSummary).toMatchObject({ total: 26, defaultProtected: false }); expect(detail.pullRequestSummary).toEqual({ open: 11 }); expect(detail.findings.map((finding) => finding.code)).toEqual(expect.arrayContaining(["DEFAULT_BRANCH_UNPROTECTED", "BRANCH_SPRAWL", "OPEN_PR_BACKLOG", "DEFAULT_BRANCH_ACTION_FAILED"])); expect(detail.findings.find((finding) => finding.code === "DEFAULT_BRANCH_ACTION_FAILED")?.evidence).toMatchObject({ runUrl: "https://gitea.example/run/1", branch: "main" }); expect(JSON.stringify(detail).length).toBeLessThan(8_000); expect(fetcher).toHaveBeenCalledTimes(7); expect(fetcher.mock.calls.map(([input]) => String(input))).toContainEqual(expect.stringContaining("branch=main")); }); it("treats Gitea's empty-repository pull-request 404 as an empty collection", async () => { const fetcher = vi.fn(async (input: string | URL | Request) => { const url = String(input); if (url.includes("/branches")) return new Response("[]", { status: 200 }); if (url.includes("/pulls")) return new Response(JSON.stringify({ message: "not found" }), { status: 404 }); if (url.includes("/actions/runs")) return new Response(JSON.stringify({ total_count: 0, workflow_runs: [] }), { status: 200 }); if (url.includes("/contents/.gitea/workflows")) return new Response(JSON.stringify({ message: "not found" }), { status: 404 }); return new Response(JSON.stringify({ id: 1, name: "Repo", full_name: "Acme/Repo", private: true, archived: false, default_branch: "main", html_url: "https://gitea.example/Acme/Repo", updated_at: "2026-08-26T00:00:00Z", permissions: { push: true }, owner: { login: "Acme" } }), { status: 200 }); }); const detail = await analyzeLinkedRepository(env, link, { includeBranches: true, includePullRequests: true, includeActions: true }, new GiteaClient("https://gitea.example.com", "secret-token", fetcher as typeof fetch)); expect(detail.pullRequestSummary).toEqual({ open: 0 }); expect(detail.findings).toEqual([{ severity: "info", code: "REPOSITORY_EMPTY", message: "Repository bevat nog geen commit of branch." }]); }); it("enforces safe worker concurrency and timeout bounds in configuration", () => { expect(() => loadEnv({ ...envInput, PROJECTBRAIN_ANALYSIS_REPOSITORY_CONCURRENCY: "3" })).toThrow(); expect(() => loadEnv({ ...envInput, PROJECTBRAIN_ANALYSIS_JOB_TIMEOUT_SECONDS: "10" })).toThrow(); }); });