Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/label-pr-review-state.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ jobs:
const codeRabbitLogin = 'coderabbitai[bot]';
const codeRabbitLogins = new Set([codeRabbitLogin, 'coderabbitai']);
const codeRabbitActiveLabel = 'coderabbit-review-active';
const codeRabbitEligibleBotLogins = new Set(['zoomote[bot]']);
const reviewGateName = 'Zoo Code / PR review gate';
const reconciliationCheckName = 'Zoo Code / reconcile PR review state';

Expand Down Expand Up @@ -660,6 +661,8 @@ jobs:
review => review.state === 'CHANGES_REQUESTED'
);
const automatedAuthor = pr.user?.type === 'Bot';
const codeRabbitEligibleAuthor = !automatedAuthor ||
codeRabbitEligibleBotLogins.has(pr.user?.login.toLowerCase());
const codeRabbitReviewComplete = freshCodeRabbitReview?.state === 'APPROVED';
const codeRabbitChangesRequested = freshCodeRabbitReview?.state === 'CHANGES_REQUESTED';
const maintainerApproval = freshMaintainerReviews
Expand All @@ -676,7 +679,7 @@ jobs:
if (codeRabbitChangesRequested || maintainerChangeRequest) {
desiredLabel = 'awaiting-author';
phase = codeRabbitChangesRequested ? 'coderabbit-changes' : 'maintainer-changes';
} else if (automatedAuthor) {
} else if (!codeRabbitEligibleAuthor) {
if (pr.draft) {
desiredLabel = null;
phase = 'draft';
Expand Down Expand Up @@ -728,7 +731,7 @@ jobs:

core.info(
`PR #${pr.number}: CI passing, reviews=${latest.size}, ` +
`coderabbit=${freshCodeRabbitReview?.state ?? (automatedAuthor ? 'optional' : 'pending')}, ` +
`coderabbit=${freshCodeRabbitReview?.state ?? (codeRabbitEligibleAuthor ? 'pending' : 'optional')}, ` +
`maintainer=${maintainerApproval?.state ?? 'pending'} → ${desiredLabel ?? '(none)'}`
);

Expand Down
46 changes: 42 additions & 4 deletions src/services/__tests__/pr-review-state-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ async function runWorkflow(options: HarnessOptions = {}) {
createCommitStatus,
createLabel,
setFailed,
info: core.info,
warning: core.warning,
getPullRequest,
listPullRequests: github.rest.pulls.list,
Expand Down Expand Up @@ -537,22 +538,59 @@ describe("PR review-state workflow", () => {
expect(latestGuide(result)).toContain("Review-state labels are managed by this workflow")
})

it("routes bot-authored PRs directly to maintainer review", async () => {
it("starts CodeRabbit for zoomote-authored PRs after required CI passes", async () => {
const result = await runWorkflow({
prAuthor: { login: "zoomote[bot]", type: "Bot" },
})

expect(result.addLabels).toHaveBeenCalledWith(expect.objectContaining({ labels: ["coderabbit-review-active"] }))
expect(result.addLabels).toHaveBeenCalledWith(expect.objectContaining({ labels: ["awaiting-coderabbit"] }))
expect(latestGateStatus(result)?.state).toBe("pending")
expect(latestGateStatus(result)?.description).toContain("Waiting for automated review")
expect(result.info).toHaveBeenCalledWith(expect.stringContaining("coderabbit=pending"))
})

it("does not start CodeRabbit for draft zoomote-authored PRs", async () => {
const result = await runWorkflow({
draft: true,
prAuthor: { login: "zoomote[bot]", type: "Bot" },
})

expect(result.addLabels).not.toHaveBeenCalledWith(
expect.objectContaining({ labels: ["coderabbit-review-active"] }),
)
expect(latestGuide(result)).toContain("Mark the PR ready")
})

it("does not start CodeRabbit for zoomote-authored PRs while required CI fails", async () => {
const result = await runWorkflow({
prAuthor: { login: "zoomote[bot]", type: "Bot" },
requiredConclusion: "failure",
})

expect(result.addLabels).not.toHaveBeenCalledWith(
expect.objectContaining({ labels: ["coderabbit-review-active"] }),
)
expect(latestGateStatus(result)?.description).toContain("Fix the failing required CI checks")
})

it("routes other bot-authored PRs directly to maintainer review", async () => {
const result = await runWorkflow({
prAuthor: { login: "dependabot[bot]", type: "Bot" },
})

expect(result.addLabels).toHaveBeenCalledWith(expect.objectContaining({ labels: ["awaiting-maintainer"] }))
expect(result.addLabels).not.toHaveBeenCalledWith(
expect.objectContaining({ labels: ["coderabbit-review-active"] }),
)
expect(latestGateStatus(result)?.state).toBe("success")
expect(latestGateStatus(result)?.description).toContain("Awaiting fresh human maintainer")
expect(result.info).toHaveBeenCalledWith(expect.stringContaining("coderabbit=optional"))
})

it("completes bot-authored PR review after human maintainer approval", async () => {
it("completes other bot-authored PR review after human maintainer approval", async () => {
const result = await runWorkflow({
prAuthor: { login: "zoomote[bot]", type: "Bot" },
prAuthor: { login: "dependabot[bot]", type: "Bot" },
permissions: { maintainer: "write" },
reviews: [
{
Expand All @@ -569,7 +607,7 @@ describe("PR review-state workflow", () => {

it("honors manually requested CodeRabbit changes on bot-authored PRs", async () => {
const result = await runWorkflow({
prAuthor: { login: "zoomote[bot]", type: "Bot" },
prAuthor: { login: "dependabot[bot]", type: "Bot" },
reviews: [
{
login: "coderabbitai[bot]",
Expand Down
Loading