Skip to content

Commit 77995f0

Browse files
committed
check merge queue is enabled or not
1 parent d78dcf6 commit 77995f0

2 files changed

Lines changed: 96 additions & 84 deletions

File tree

src/__tests__/index.test.js

Lines changed: 68 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ describe("enqueue-pullrequest action", () => {
6969
};
7070
}
7171

72+
// Mocks for each GraphQL call in order.
73+
const mqEnabled = { repository: { mergeQueue: { id: "MQ_1" } } };
74+
const mqDisabled = { repository: { mergeQueue: null } };
75+
76+
function mockEnqueue(id = "MQE_1", position = 1) {
77+
return { enqueuePullRequest: { mergeQueueEntry: { id, state: "QUEUED", position } } };
78+
}
79+
7280
// Loads the action module, which immediately calls run().
7381
async function runAction() {
7482
require("../index");
@@ -81,25 +89,41 @@ describe("enqueue-pullrequest action", () => {
8189
test("enqueues an eligible PR", async () => {
8290
setupInputs();
8391
mockOctokit.graphql
84-
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
85-
.mockResolvedValueOnce({
86-
enqueuePullRequest: {
87-
mergeQueueEntry: { id: "MQE_1", state: "QUEUED", position: 1 },
88-
},
89-
});
92+
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } }) // getPRDetails
93+
.mockResolvedValueOnce(mqEnabled) // isMergeQueueEnabled
94+
.mockResolvedValueOnce(mockEnqueue()); // enqueuePullRequest
9095

9196
await runAction();
9297

93-
expect(mockOctokit.graphql).toHaveBeenCalledTimes(2);
94-
// Second call must be the enqueuePullRequest mutation with the PR node ID
98+
expect(mockOctokit.graphql).toHaveBeenCalledTimes(3);
9599
expect(mockOctokit.graphql).toHaveBeenNthCalledWith(
96100
2,
101+
expect.stringContaining("MergeQueueEnabled"),
102+
expect.objectContaining({ branch: "main" })
103+
);
104+
expect(mockOctokit.graphql).toHaveBeenNthCalledWith(
105+
3,
97106
expect.stringContaining("enqueuePullRequest"),
98107
{ prId: "PR_abc123" }
99108
);
100109
expect(core.setFailed).not.toHaveBeenCalled();
101110
});
102111

112+
test("warns and skips when merge queue is not enabled for the base branch", async () => {
113+
setupInputs();
114+
mockOctokit.graphql
115+
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
116+
.mockResolvedValueOnce(mqDisabled);
117+
118+
await runAction();
119+
120+
expect(mockOctokit.graphql).toHaveBeenCalledTimes(2);
121+
expect(core.warning).toHaveBeenCalledWith(
122+
expect.stringContaining("merge queue is not enabled")
123+
);
124+
expect(core.setFailed).not.toHaveBeenCalled();
125+
});
126+
103127
test("skips a closed PR without calling enqueue", async () => {
104128
setupInputs();
105129
mockOctokit.graphql.mockResolvedValueOnce({
@@ -127,20 +151,15 @@ describe("enqueue-pullrequest action", () => {
127151
test("enqueues a draft PR when skip-drafts=false", async () => {
128152
setupInputs({ "skip-drafts": "false" });
129153
mockOctokit.graphql
130-
.mockResolvedValueOnce({
131-
repository: { pullRequest: makePRPayload({ isDraft: true }) },
132-
})
133-
.mockResolvedValueOnce({
134-
enqueuePullRequest: {
135-
mergeQueueEntry: { id: "MQE_1", state: "QUEUED", position: 1 },
136-
},
137-
});
154+
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload({ isDraft: true }) } })
155+
.mockResolvedValueOnce(mqEnabled)
156+
.mockResolvedValueOnce(mockEnqueue());
138157

139158
await runAction();
140159

141-
expect(mockOctokit.graphql).toHaveBeenCalledTimes(2);
160+
expect(mockOctokit.graphql).toHaveBeenCalledTimes(3);
142161
expect(mockOctokit.graphql).toHaveBeenNthCalledWith(
143-
2,
162+
3,
144163
expect.stringContaining("enqueuePullRequest"),
145164
expect.anything()
146165
);
@@ -161,20 +180,15 @@ describe("enqueue-pullrequest action", () => {
161180
test("enqueues PR with no label requirement when label input is empty", async () => {
162181
setupInputs({ label: "" });
163182
mockOctokit.graphql
164-
.mockResolvedValueOnce({
165-
repository: { pullRequest: makePRPayload({ labels: { nodes: [] } }) },
166-
})
167-
.mockResolvedValueOnce({
168-
enqueuePullRequest: {
169-
mergeQueueEntry: { id: "MQE_2", state: "QUEUED", position: 1 },
170-
},
171-
});
183+
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload({ labels: { nodes: [] } }) } })
184+
.mockResolvedValueOnce(mqEnabled)
185+
.mockResolvedValueOnce(mockEnqueue("MQE_2"));
172186

173187
await runAction();
174188

175-
expect(mockOctokit.graphql).toHaveBeenCalledTimes(2);
189+
expect(mockOctokit.graphql).toHaveBeenCalledTimes(3);
176190
expect(mockOctokit.graphql).toHaveBeenNthCalledWith(
177-
2,
191+
3,
178192
expect.stringContaining("enqueuePullRequest"),
179193
expect.anything()
180194
);
@@ -185,9 +199,7 @@ describe("enqueue-pullrequest action", () => {
185199
mockOctokit.graphql.mockResolvedValueOnce({
186200
repository: {
187201
pullRequest: makePRPayload({
188-
labels: {
189-
nodes: [{ name: "enqueue-pullrequest" }, { name: "wip" }],
190-
},
202+
labels: { nodes: [{ name: "enqueue-pullrequest" }, { name: "wip" }] },
191203
}),
192204
},
193205
});
@@ -216,9 +228,7 @@ describe("enqueue-pullrequest action", () => {
216228
test("skips PR with insufficient required approvals", async () => {
217229
setupInputs({ "required-approvals": "2" });
218230
mockOctokit.graphql.mockResolvedValueOnce({
219-
repository: {
220-
pullRequest: makePRPayload({ reviews: { totalCount: 1 } }),
221-
},
231+
repository: { pullRequest: makePRPayload({ reviews: { totalCount: 1 } }) },
222232
});
223233

224234
await runAction();
@@ -230,28 +240,19 @@ describe("enqueue-pullrequest action", () => {
230240
test("enqueues PR that meets the required approvals threshold", async () => {
231241
setupInputs({ "required-approvals": "2" });
232242
mockOctokit.graphql
233-
.mockResolvedValueOnce({
234-
repository: {
235-
pullRequest: makePRPayload({ reviews: { totalCount: 2 } }),
236-
},
237-
})
238-
.mockResolvedValueOnce({
239-
enqueuePullRequest: {
240-
mergeQueueEntry: { id: "MQE_1", state: "QUEUED", position: 1 },
241-
},
242-
});
243+
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload({ reviews: { totalCount: 2 } }) } })
244+
.mockResolvedValueOnce(mqEnabled)
245+
.mockResolvedValueOnce(mockEnqueue());
243246

244247
await runAction();
245248

246-
expect(mockOctokit.graphql).toHaveBeenCalledTimes(2);
249+
expect(mockOctokit.graphql).toHaveBeenCalledTimes(3);
247250
});
248251

249252
test("skips PR targeting a branch not in base-branches list", async () => {
250253
setupInputs({ "base-branches": "main,master" });
251254
mockOctokit.graphql.mockResolvedValueOnce({
252-
repository: {
253-
pullRequest: makePRPayload({ baseRefName: "develop" }),
254-
},
255+
repository: { pullRequest: makePRPayload({ baseRefName: "develop" }) },
255256
});
256257

257258
await runAction();
@@ -268,6 +269,7 @@ describe("enqueue-pullrequest action", () => {
268269
setupInputs();
269270
mockOctokit.graphql
270271
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
272+
.mockResolvedValueOnce(mqEnabled)
271273
.mockRejectedValueOnce(new Error("Branch protection not enabled"));
272274

273275
await runAction();
@@ -286,9 +288,7 @@ describe("enqueue-pullrequest action", () => {
286288

287289
await runAction();
288290

289-
expect(core.warning).toHaveBeenCalledWith(
290-
expect.stringContaining("not found")
291-
);
291+
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining("not found"));
292292
});
293293

294294
test("logs warning and continues when getPRDetails throws", async () => {
@@ -316,11 +316,8 @@ describe("enqueue-pullrequest action", () => {
316316
};
317317
mockOctokit.graphql
318318
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
319-
.mockResolvedValueOnce({
320-
enqueuePullRequest: {
321-
mergeQueueEntry: { id: "MQE_1", state: "QUEUED", position: 1 },
322-
},
323-
});
319+
.mockResolvedValueOnce(mqEnabled)
320+
.mockResolvedValueOnce(mockEnqueue());
324321

325322
await runAction();
326323

@@ -339,11 +336,8 @@ describe("enqueue-pullrequest action", () => {
339336
};
340337
mockOctokit.graphql
341338
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
342-
.mockResolvedValueOnce({
343-
enqueuePullRequest: {
344-
mergeQueueEntry: { id: "MQE_1", state: "QUEUED", position: 1 },
345-
},
346-
});
339+
.mockResolvedValueOnce(mqEnabled)
340+
.mockResolvedValueOnce(mockEnqueue());
347341

348342
await runAction();
349343

@@ -357,20 +351,16 @@ describe("enqueue-pullrequest action", () => {
357351
setupInputs();
358352
github.context = {
359353
eventName: "check_run",
360-
payload: {
361-
check_run: { pull_requests: [{ number: 10 }, { number: 11 }] },
362-
},
354+
payload: { check_run: { pull_requests: [{ number: 10 }, { number: 11 }] } },
363355
repo: { owner: "acme", repo: "my-repo" },
364356
};
365357
mockOctokit.graphql
366358
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
367-
.mockResolvedValueOnce({
368-
enqueuePullRequest: { mergeQueueEntry: { id: "MQE_1", state: "QUEUED", position: 1 } },
369-
})
359+
.mockResolvedValueOnce(mqEnabled)
360+
.mockResolvedValueOnce(mockEnqueue("MQE_1"))
370361
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
371-
.mockResolvedValueOnce({
372-
enqueuePullRequest: { mergeQueueEntry: { id: "MQE_2", state: "QUEUED", position: 2 } },
373-
});
362+
.mockResolvedValueOnce(mqEnabled)
363+
.mockResolvedValueOnce(mockEnqueue("MQE_2", 2));
374364

375365
await runAction();
376366

@@ -388,16 +378,13 @@ describe("enqueue-pullrequest action", () => {
388378
setupInputs();
389379
github.context = {
390380
eventName: "check_suite",
391-
payload: {
392-
check_suite: { pull_requests: [{ number: 20 }] },
393-
},
381+
payload: { check_suite: { pull_requests: [{ number: 20 }] } },
394382
repo: { owner: "acme", repo: "my-repo" },
395383
};
396384
mockOctokit.graphql
397385
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
398-
.mockResolvedValueOnce({
399-
enqueuePullRequest: { mergeQueueEntry: { id: "MQE_1", state: "QUEUED", position: 1 } },
400-
});
386+
.mockResolvedValueOnce(mqEnabled)
387+
.mockResolvedValueOnce(mockEnqueue());
401388

402389
await runAction();
403390

@@ -417,21 +404,19 @@ describe("enqueue-pullrequest action", () => {
417404
mockOctokit.paginate.mockResolvedValue([{ number: 5 }, { number: 6 }]);
418405
mockOctokit.graphql
419406
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
420-
.mockResolvedValueOnce({
421-
enqueuePullRequest: { mergeQueueEntry: { id: "MQE_1", state: "QUEUED", position: 1 } },
422-
})
407+
.mockResolvedValueOnce(mqEnabled)
408+
.mockResolvedValueOnce(mockEnqueue("MQE_1"))
423409
.mockResolvedValueOnce({ repository: { pullRequest: makePRPayload() } })
424-
.mockResolvedValueOnce({
425-
enqueuePullRequest: { mergeQueueEntry: { id: "MQE_2", state: "QUEUED", position: 2 } },
426-
});
410+
.mockResolvedValueOnce(mqEnabled)
411+
.mockResolvedValueOnce(mockEnqueue("MQE_2", 2));
427412

428413
await runAction();
429414

430415
expect(mockOctokit.paginate).toHaveBeenCalledWith(
431416
mockOctokit.rest.pulls.list,
432417
expect.objectContaining({ state: "open" })
433418
);
434-
expect(mockOctokit.graphql).toHaveBeenCalledTimes(4);
419+
expect(mockOctokit.graphql).toHaveBeenCalledTimes(6);
435420
});
436421

437422
test("workflow_dispatch event with no open PRs logs and exits cleanly", async () => {

src/index.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ async function enqueue(octokit, prNodeId) {
7777
return result.enqueuePullRequest?.mergeQueueEntry ?? null;
7878
}
7979

80+
/** Returns true if the merge queue is enabled for the given branch. */
81+
async function isMergeQueueEnabled(octokit, owner, repo, branch) {
82+
const { repository } = await octokit.graphql(
83+
`
84+
query MergeQueueEnabled($owner: String!, $repo: String!, $branch: String!) {
85+
repository(owner: $owner, name: $repo) {
86+
mergeQueue(branch: $branch) {
87+
id
88+
}
89+
}
90+
}
91+
`,
92+
{ owner, repo, branch }
93+
);
94+
return repository?.mergeQueue != null;
95+
}
96+
8097
// ─── Eligibility checks ───────────────────────────────────────────────────────
8198

8299
/**
@@ -165,7 +182,17 @@ async function processPR(octokit, owner, repo, prNumber, config) {
165182
return;
166183
}
167184

168-
core.info("Eligible — adding to merge queue…");
185+
core.info("Eligible — checking merge queue is enabled…");
186+
187+
const mqEnabled = await isMergeQueueEnabled(octokit, owner, repo, pr.baseRefName);
188+
if (!mqEnabled) {
189+
core.warning(
190+
`PR #${prNumber}: merge queue is not enabled for branch "${pr.baseRefName}" — skipping`
191+
);
192+
return;
193+
}
194+
195+
core.info("Adding to merge queue…");
169196

170197
try {
171198
const entry = await enqueue(octokit, pr.id);

0 commit comments

Comments
 (0)