Skip to content
Merged
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
3 changes: 1 addition & 2 deletions actions/setup/js/update_pull_request_branches.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,10 @@ async function main() {
let skippedCount = 0;
let failedCount = 0;

for (let i = 0; i < mergeablePullRequests.length; i++) {
for (const [i, pullNumber] of mergeablePullRequests.entries()) {
if (i > 0) {
await sleep(UPDATE_DELAY_MS);
}
const pullNumber = mergeablePullRequests[i];
try {
core.info(`Updating branch for PR #${pullNumber}`);
await updatePullRequestBranch(owner, repo, pullNumber);
Expand Down
34 changes: 34 additions & 0 deletions actions/setup/js/update_pull_request_branches.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,40 @@ describe("update_pull_request_branches", () => {
expect(moduleUnderTest.isNonFatalUpdateBranchError(new Error("Something else went wrong"))).toBe(false);
});

it("skips closed pull requests when filtering mergeable pull requests", async () => {
mockGithub.rest.pulls.get.mockImplementation(async ({ pull_number }) => {
if (pull_number === 1) return { data: { state: "closed", mergeable: true, draft: false, head: { repo: { full_name: "owner/repo" } } } };
return { data: { state: "open", mergeable: true, draft: false, head: { repo: { full_name: "owner/repo" } } } };
});

const result = await moduleUnderTest.filterMergeablePullRequests("owner", "repo", [1, 2]);

expect(result).toEqual([2]);
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Skipping PR #1"));
});

it("skips pull requests where mergeable is null (GitHub pending computation)", async () => {
mockGithub.rest.pulls.get.mockResolvedValue({
data: { state: "open", mergeable: null, draft: false, head: { repo: { full_name: "owner/repo" } } },
});

const result = await moduleUnderTest.filterMergeablePullRequests("owner", "repo", [5]);

expect(result).toEqual([]);
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("mergeable=null"));
});

it("does not treat non-object errors as non-fatal", () => {
expect(moduleUnderTest.isNonFatalUpdateBranchError("some string error")).toBe(false);
expect(moduleUnderTest.isNonFatalUpdateBranchError(null)).toBe(false);
expect(moduleUnderTest.isNonFatalUpdateBranchError(42)).toBe(false);
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Three distinct primitive inputs (string, null, number) are asserted in a single it() block — if the first assertion fails, the other two inputs go untested in that run.

💡 Consider it.each for better failure isolation
it.each([
  ["some string error"],
  [null],
  [42],
])("does not treat %s as a non-fatal error", (input) => {
  expect(moduleUnderTest.isNonFatalUpdateBranchError(input)).toBe(false);
});

This is a minor style note — the current approach still validates the behaviour correctly, and all cases fail fast on the same function.


it("does not treat 404 status errors as non-fatal", () => {
const err = Object.assign(new Error("Not Found"), { status: 404 });
expect(moduleUnderTest.isNonFatalUpdateBranchError(err)).toBe(false);
});

it("filters out non-integer pull request numbers", async () => {
mockGithub.paginate.mockResolvedValue([{ number: 1 }, { number: "bad" }, { number: null }, { number: 2 }]);
mockGithub.rest.pulls.get.mockResolvedValue({
Expand Down
Loading