Spaces:
Running
Running
| // Unit tests for the local GitPilot bridge (Batch 3). | |
| // | |
| // Run with: npm run test (node --test with type-stripping and the @/ alias resolver). | |
| // Covers the pure URL/payload helpers and the probe/send behaviour against a | |
| // stubbed fetch — including graceful failure when local GitPilot is down. | |
| import assert from "node:assert/strict"; | |
| import { afterEach, describe, it } from "node:test"; | |
| import { | |
| DEFAULT_LOCAL_GITPILOT_URL, | |
| buildMatrixRunPayload, | |
| cloudArtifactUrl, | |
| createCloudRun, | |
| fetchDiffText, | |
| findingsFromReport, | |
| getCloudRun, | |
| isCloudRunTerminal, | |
| localGitPilotBaseUrl, | |
| localHealthUrl, | |
| localRunsUrl, | |
| openPr, | |
| parseDiffLines, | |
| probeLocalGitPilot, | |
| repairCloudRun, | |
| sendToLocalGitPilot, | |
| stripTrailingSlash, | |
| validateCloudRun, | |
| type CloudValidationResult, | |
| } from "@/lib/gitpilot-client"; | |
| const realFetch = globalThis.fetch; | |
| afterEach(() => { | |
| globalThis.fetch = realFetch; | |
| }); | |
| describe("gitpilot-client · url helpers", () => { | |
| it("strips trailing slashes", () => { | |
| assert.equal(stripTrailingSlash("http://localhost:8000/"), "http://localhost:8000"); | |
| assert.equal(stripTrailingSlash("http://localhost:8000///"), "http://localhost:8000"); | |
| }); | |
| it("builds the matrix bridge health + runs urls", () => { | |
| assert.equal(localHealthUrl("http://localhost:8000"), "http://localhost:8000/api/matrix/health"); | |
| assert.equal(localRunsUrl("http://localhost:8000/"), "http://localhost:8000/api/matrix/runs"); | |
| }); | |
| it("defaults the base url to localhost:8000", () => { | |
| assert.equal(DEFAULT_LOCAL_GITPILOT_URL, "http://localhost:8000"); | |
| assert.equal(localGitPilotBaseUrl(), "http://localhost:8000"); | |
| }); | |
| }); | |
| describe("gitpilot-client · buildMatrixRunPayload", () => { | |
| it("maps the contract and tags coder/source", () => { | |
| const payload = buildMatrixRunPayload({ | |
| bundleUrl: "https://api.ruslanmv.com/v1/matrix-bundles/b1", | |
| projectName: "Starter", | |
| taskId: "TASK-001", | |
| prompt: "do the thing", | |
| allowedFiles: ["src/**"], | |
| forbiddenFiles: ["MATRIX_STANDARDS.lock"], | |
| validationCommands: ["pytest -q"], | |
| }); | |
| assert.equal(payload.bundle_url, "https://api.ruslanmv.com/v1/matrix-bundles/b1"); | |
| assert.equal(payload.task_id, "TASK-001"); | |
| assert.equal(payload.coder, "gitpilot"); | |
| assert.equal(payload.source, "matrix-builder"); | |
| assert.equal(payload.mode, "ask"); // default | |
| assert.deepEqual(payload.allowed_files, ["src/**"]); | |
| // Copies, not references to the readonly inputs. | |
| assert.deepEqual(payload.forbidden_files, ["MATRIX_STANDARDS.lock"]); | |
| }); | |
| }); | |
| describe("gitpilot-client · probeLocalGitPilot", () => { | |
| it("returns true on a healthy 200", async () => { | |
| globalThis.fetch = (async () => | |
| new Response(JSON.stringify({ status: "ok" }), { status: 200 })) as typeof fetch; | |
| assert.equal(await probeLocalGitPilot("http://localhost:8000", 200), true); | |
| }); | |
| it("returns false on a non-200", async () => { | |
| globalThis.fetch = (async () => new Response("nope", { status: 404 })) as typeof fetch; | |
| assert.equal(await probeLocalGitPilot("http://localhost:8000", 200), false); | |
| }); | |
| it("returns false (never throws) when the server is unreachable", async () => { | |
| globalThis.fetch = (async () => { | |
| throw new Error("ECONNREFUSED"); | |
| }) as typeof fetch; | |
| assert.equal(await probeLocalGitPilot("http://localhost:8000", 200), false); | |
| }); | |
| }); | |
| describe("gitpilot-client · sendToLocalGitPilot", () => { | |
| const payload = buildMatrixRunPayload({ | |
| bundleUrl: "https://api.ruslanmv.com/v1/matrix-bundles/b1", | |
| projectName: "Starter", | |
| taskId: "TASK-001", | |
| prompt: "do the thing", | |
| allowedFiles: ["src/**"], | |
| forbiddenFiles: ["MATRIX_STANDARDS.lock"], | |
| validationCommands: ["pytest -q"], | |
| }); | |
| it("posts the run and returns the queued result", async () => { | |
| let seenUrl = ""; | |
| let seenBody: unknown = null; | |
| globalThis.fetch = (async (url: string, init: RequestInit) => { | |
| seenUrl = url; | |
| seenBody = JSON.parse(String(init.body)); | |
| return new Response( | |
| JSON.stringify({ run_id: "gp-run-abc", status: "queued", url: "http://localhost:8000/api/v1/gitpilot/runs/gp-run-abc" }), | |
| { status: 200 }, | |
| ); | |
| }) as typeof fetch; | |
| const result = await sendToLocalGitPilot(payload, "http://localhost:8000"); | |
| assert.equal(seenUrl, "http://localhost:8000/api/matrix/runs"); | |
| assert.deepEqual((seenBody as { coder: string }).coder, "gitpilot"); | |
| assert.equal(result.run_id, "gp-run-abc"); | |
| assert.equal(result.status, "queued"); | |
| }); | |
| it("throws on a non-2xx so the caller can show a graceful message", async () => { | |
| globalThis.fetch = (async () => new Response("boom", { status: 500 })) as typeof fetch; | |
| await assert.rejects(() => sendToLocalGitPilot(payload, "http://localhost:8000")); | |
| }); | |
| }); | |
| describe("gitpilot-client · cloud run (Matrix Builder backend)", () => { | |
| it("isCloudRunTerminal recognises terminal states only", () => { | |
| for (const s of ["completed", "blocked", "error", "needs_approval"]) { | |
| assert.equal(isCloudRunTerminal(s), true); | |
| } | |
| for (const s of ["queued", "running"]) { | |
| assert.equal(isCloudRunTerminal(s), false); | |
| } | |
| }); | |
| it("createCloudRun POSTs to the bundle's MB endpoint and returns the run", async () => { | |
| let seenUrl = ""; | |
| globalThis.fetch = (async (url: string) => { | |
| seenUrl = url; | |
| return new Response(JSON.stringify({ run_id: "gp-run-1", status: "queued", url: "x" }), { status: 200 }); | |
| }) as typeof fetch; | |
| const r = await createCloudRun("bundle_demo", { task_id: "T1" }); | |
| assert.match(seenUrl, /\/api\/v1\/bundles\/bundle_demo\/gitpilot\/runs$/); | |
| assert.equal(r.run_id, "gp-run-1"); | |
| assert.equal(r.status, "queued"); | |
| }); | |
| it("getCloudRun reads status from the MB endpoint", async () => { | |
| globalThis.fetch = (async () => | |
| new Response( | |
| JSON.stringify({ | |
| run_id: "gp-run-1", | |
| status: "completed", | |
| summary: "done", | |
| diff_url: "/api/v1/gitpilot/runs/gp-run-1/diff", | |
| logs_url: "/api/v1/gitpilot/runs/gp-run-1/logs", | |
| test_status: "passed", | |
| changed_files: ["tests/test_health.py"], | |
| }), | |
| { status: 200 }, | |
| )) as typeof fetch; | |
| const s = await getCloudRun("gp-run-1"); | |
| assert.equal(s.status, "completed"); | |
| assert.equal(s.test_status, "passed"); | |
| assert.deepEqual(s.changed_files, ["tests/test_health.py"]); | |
| }); | |
| it("cloudArtifactUrl prefixes the MB base and passes null through", () => { | |
| assert.equal(cloudArtifactUrl(null), null); | |
| // apiBaseUrl defaults to "/api/builder" in the test env (no NEXT_PUBLIC_API_BASE_URL). | |
| assert.match(cloudArtifactUrl("/api/v1/gitpilot/runs/x/diff") ?? "", /\/api\/v1\/gitpilot\/runs\/x\/diff$/); | |
| }); | |
| }); | |
| describe("gitpilot-client · validation gate + repair (Batches 7 & 8)", () => { | |
| const approved: CloudValidationResult = { | |
| run_id: "gp-run-1", | |
| gate: { status: "approved", can_commit: true, can_repair: false, blocked: false }, | |
| report: { status: "approved", score: 100, violations: [], repair_prompt: null }, | |
| }; | |
| it("validateCloudRun POSTs to the run's validate endpoint and returns the gate", async () => { | |
| let seenUrl = ""; | |
| globalThis.fetch = (async (url: string) => { | |
| seenUrl = url; | |
| return new Response(JSON.stringify(approved), { status: 200 }); | |
| }) as typeof fetch; | |
| const result = await validateCloudRun("bundle_demo", "gp-run-1"); | |
| assert.match(seenUrl, /\/api\/v1\/bundles\/bundle_demo\/gitpilot\/runs\/gp-run-1\/validate$/); | |
| assert.equal(result.gate.can_commit, true); | |
| }); | |
| it("repairCloudRun POSTs findings and returns a new child run", async () => { | |
| let seenUrl = ""; | |
| let seenBody: { repair_prompt?: string } = {}; | |
| globalThis.fetch = (async (url: string, init: RequestInit) => { | |
| seenUrl = url; | |
| seenBody = JSON.parse(String(init.body)); | |
| return new Response(JSON.stringify({ run_id: "gp-run-2", status: "queued", url: "x" }), { status: 200 }); | |
| }) as typeof fetch; | |
| const child = await repairCloudRun("bundle_demo", "gp-run-1", { repair_prompt: "fix it" }); | |
| assert.match(seenUrl, /\/gitpilot\/runs\/gp-run-1\/repair$/); | |
| assert.equal(seenBody.repair_prompt, "fix it"); | |
| assert.equal(child.run_id, "gp-run-2"); | |
| }); | |
| it("findingsFromReport flattens violations into rule: message strings", () => { | |
| const findings = findingsFromReport({ | |
| status: "needs-repair", | |
| score: 60, | |
| repair_prompt: "x", | |
| violations: [ | |
| { rule_id: "R1", severity: "high", message: "outside allowed_paths" }, | |
| { rule_id: "R2", severity: "medium", message: "missing test" }, | |
| ], | |
| }); | |
| assert.deepEqual(findings, ["R1: outside allowed_paths", "R2: missing test"]); | |
| }); | |
| }); | |
| describe("gitpilot-client · PR flow + diff viewer (Batch 11)", () => { | |
| it("parseDiffLines classifies add/del/hunk/meta/context", () => { | |
| const lines = parseDiffLines( | |
| ["--- a/x", "+++ b/x", "@@ -1 +1 @@", "+added", "-removed", " context"].join("\n"), | |
| ); | |
| assert.deepEqual( | |
| lines.map((l) => l.kind), | |
| ["meta", "meta", "hunk", "add", "del", "context"], | |
| ); | |
| }); | |
| it("openPr POSTs to the run's PR endpoint and returns the pr_url", async () => { | |
| let seenUrl = ""; | |
| globalThis.fetch = (async (url: string) => { | |
| seenUrl = url; | |
| return new Response( | |
| JSON.stringify({ run_id: "gp-run-1", pr_url: "https://github.com/o/r/pull/1", status: "draft", message: "" }), | |
| { status: 200 }, | |
| ); | |
| }) as typeof fetch; | |
| const r = await openPr("bundle_demo", "gp-run-1", { title: "x" }); | |
| assert.match(seenUrl, /\/api\/v1\/bundles\/bundle_demo\/gitpilot\/runs\/gp-run-1\/pr$/); | |
| assert.equal(r.pr_url, "https://github.com/o/r/pull/1"); | |
| }); | |
| it("openPr surfaces the 409 'Matrix approval required' as a clear error", async () => { | |
| globalThis.fetch = (async () => new Response("{}", { status: 409 })) as typeof fetch; | |
| await assert.rejects(() => openPr("b", "r"), /Matrix approval required/); | |
| }); | |
| it("fetchDiffText fetches the proxied diff text", async () => { | |
| globalThis.fetch = (async () => new Response("--- a\n+++ b\n", { status: 200 })) as typeof fetch; | |
| const text = await fetchDiffText("/api/v1/gitpilot/runs/r/diff"); | |
| assert.match(text, /\+\+\+ b/); | |
| }); | |
| }); | |