import { Effect, Exit } from "effect"; import { describe, expect, test } from "vitest"; import { decideGitLifecycle, inspectGitWorkspace, makeCommitMessage, validatePullRequestMetadata, } from "./git"; const inspectionInput = { branch: "work/issue-5", diff: "diff --git a/file.ts b/file.ts", status: " M file.ts", }; const inspection = await Effect.runPromise( inspectGitWorkspace(inspectionInput) ); describe("Git lifecycle primitives", () => { test("parses workspace status and treats a diff as publishable changes", () => { expect(inspection).toMatchObject({ branch: "work/issue-5", hasChanges: true, status: [{ index: " ", path: "file.ts", worktree: "M" }], }); }); test("requires verification before any publish action", async () => { const result = await Effect.runPromiseExit( decideGitLifecycle({ baseBranch: "main", inspection, pushed: false, verification: "not-run", }) ); expect(Exit.isFailure(result)).toBe(true); if (Exit.isFailure(result)) { const failure = result.cause as unknown as { readonly reasons: readonly [ { readonly error: { readonly reason: string } }, ]; }; expect(failure.reasons[0]?.error.reason).toBe("VerificationRequired"); } }); test("never publishes the default branch", async () => { const result = await Effect.runPromiseExit( decideGitLifecycle({ baseBranch: "work/issue-5", inspection, pushed: false, verification: "passed", }) ); expect(Exit.isFailure(result)).toBe(true); if (Exit.isFailure(result)) { const failure = result.cause as unknown as { readonly reasons: readonly [ { readonly error: { readonly reason: string } }, ]; }; expect(failure.reasons[0]?.error.reason).toBe("InvalidBranch"); } }); test("moves verified changes through commit, push, PR, then manual merge", async () => { const commit = await Effect.runPromise( decideGitLifecycle({ baseBranch: "main", inspection, pushed: false, verification: "passed", }) ); expect(commit).toEqual({ decision: "commit", status: "committed" }); const push = await Effect.runPromise( decideGitLifecycle({ baseBranch: "main", commitSha: "abc123", inspection: { ...inspection, hasChanges: false, status: [] }, pushed: false, verification: "passed", }) ); expect(push).toEqual({ decision: "push", status: "pushed" }); const pr = await Effect.runPromise( decideGitLifecycle({ baseBranch: "main", commitSha: "abc123", inspection: { ...inspection, hasChanges: false, status: [] }, pushed: true, verification: "passed", }) ); expect(pr).toEqual({ decision: "create_pull_request", status: "pull_request_open", }); const merge = await Effect.runPromise( decideGitLifecycle({ baseBranch: "main", commitSha: "abc123", inspection: { ...inspection, hasChanges: false, status: [] }, pullRequest: { baseBranch: "main", branch: "work/issue-5", number: 5, status: "open", url: "https://git.openputer.com/puter/zopu-code/pulls/5", }, pushed: true, verification: "passed", }) ); expect(merge).toEqual({ decision: "manual_merge", status: "pull_request_open", }); }); test("validates PR metadata and keeps merge manual", async () => { const metadata = await Effect.runPromise( validatePullRequestMetadata({ baseBranch: "main", branch: "work/issue-5", number: 5, status: "open", url: "https://git.openputer.com/puter/zopu-code/pulls/5", }) ); expect(metadata.status).toBe("open"); expect(makeCommitMessage(5, "Ship Git lifecycle")).toBe( "feat(issue-5): Ship Git lifecycle" ); }); });