146 lines
4.0 KiB
TypeScript
146 lines
4.0 KiB
TypeScript
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"
|
|
);
|
|
});
|
|
});
|