Skip to content

Commit b32f118

Browse files
Copilotpelikhan
andauthored
Preserve workflow approval retries
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent bc028d2 commit b32f118

2 files changed

Lines changed: 53 additions & 7 deletions

File tree

‎actions/setup/js/approve_workflow_run.cjs‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function parsePositiveInt(value) {
3232
* @returns {Set<number>}
3333
*/
3434
function parseAllowedPullRequests(value) {
35-
const parsed = (Array.isArray(value) ? value : [value]).map(parsePositiveInt).filter(value => value !== undefined);
35+
const parsed = (Array.isArray(value) ? value : [value]).map(parsePositiveInt).filter(candidate => candidate !== undefined);
3636
return new Set(parsed);
3737
}
3838

@@ -73,6 +73,9 @@ async function isForkPullRequest(githubClient, pullRequestNumber) {
7373
repo: context.repo.repo,
7474
pull_number: pullRequestNumber,
7575
});
76+
if (pullRequest?.head?.repo === null) {
77+
throw new Error(`Cannot approve pull request #${pullRequestNumber}: its fork repository is unavailable`);
78+
}
7679
if (typeof pullRequest?.head?.repo?.fork !== "boolean") {
7780
throw new Error(`Unable to verify fork status for pull request #${pullRequestNumber}`);
7881
}
@@ -179,12 +182,16 @@ async function main(config = {}) {
179182
}
180183

181184
processedCount++;
182-
183-
await githubClient.rest.actions.approveWorkflowRun({
184-
owner: context.repo.owner,
185-
repo: context.repo.repo,
186-
run_id: runId,
187-
});
185+
try {
186+
await githubClient.rest.actions.approveWorkflowRun({
187+
owner: context.repo.owner,
188+
repo: context.repo.repo,
189+
run_id: runId,
190+
});
191+
} catch (error) {
192+
processedCount--;
193+
throw error;
194+
}
188195

189196
core.info(`Approved workflow run ${runId}: ${run.html_url}`);
190197
return { success: true, run_id: runId, url: run.html_url };

‎actions/setup/js/approve_workflow_run.test.cjs‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,35 @@ describe("approve_workflow_run", () => {
144144
expect(mockApproveWorkflowRun).toHaveBeenCalledTimes(1);
145145
});
146146

147+
it("rejects a run when any associated pull request is from a fork by default", async () => {
148+
mockGetWorkflowRun.mockResolvedValue({
149+
data: { ...pendingPullRequestRun, pull_requests: [{ number: 42 }, { number: 43 }] },
150+
});
151+
mockGetPullRequest.mockImplementation(async ({ pull_number: pullRequestNumber }) => ({
152+
data: { head: { repo: { fork: pullRequestNumber === 43 } } },
153+
}));
154+
const { main } = require("./approve_workflow_run.cjs");
155+
const handler = await main(externalTokenConfig);
156+
157+
const result = await handler({ run_id: 123 }, {});
158+
159+
expect(result.success).toBe(false);
160+
expect(result.error).toContain("set fork: true");
161+
expect(mockApproveWorkflowRun).not.toHaveBeenCalled();
162+
});
163+
164+
it("rejects pull requests with an unavailable fork repository", async () => {
165+
mockGetPullRequest.mockResolvedValue({ data: { head: { repo: null } } });
166+
const { main } = require("./approve_workflow_run.cjs");
167+
const handler = await main(externalTokenConfig);
168+
169+
const result = await handler({ run_id: 123 }, {});
170+
171+
expect(result.success).toBe(false);
172+
expect(result.error).toContain("fork repository is unavailable");
173+
expect(mockApproveWorkflowRun).not.toHaveBeenCalled();
174+
});
175+
147176
it("rejects pull_request_target events before accessing GitHub", async () => {
148177
global.context.eventName = "pull_request_target";
149178
const { main } = require("./approve_workflow_run.cjs");
@@ -279,4 +308,14 @@ describe("approve_workflow_run", () => {
279308
expect((await handler({ run_id: 123 }, {})).success).toBe(true);
280309
expect(mockApproveWorkflowRun).toHaveBeenCalledTimes(1);
281310
});
311+
312+
it("does not consume the maximum when approval fails", async () => {
313+
mockApproveWorkflowRun.mockRejectedValueOnce(new Error("temporary API failure")).mockResolvedValueOnce({ status: 201 });
314+
const { main } = require("./approve_workflow_run.cjs");
315+
const handler = await main({ ...externalTokenConfig, max: 1 });
316+
317+
expect((await handler({ run_id: 122 }, {})).success).toBe(false);
318+
expect((await handler({ run_id: 123 }, {})).success).toBe(true);
319+
expect(mockApproveWorkflowRun).toHaveBeenCalledTimes(2);
320+
});
282321
});

0 commit comments

Comments
 (0)