From 2a6119614d5d605f29f4ddef559c262e04f33f3b Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 6 Nov 2026 11:19:07 +0100 Subject: [PATCH 1/2] fix(success): send GraphQL chunk queries one after another For large releases, the success step sent one getAssociatedPRs query for each 100 commits, all at the same time. GitHub can fail these parallel queries with a timeout or a 502 error. Run the chunks one after another. Closes #1017 --- lib/success.js | 13 ++++---- test/success.test.js | 70 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/lib/success.js b/lib/success.js index 48246f29..3cc7a352 100644 --- a/lib/success.js +++ b/lib/success.js @@ -333,9 +333,11 @@ export default async function success(pluginConfig, context, { Octokit }) { } /** - * In order to speed up a function call that handles a big array of items, we split up the - * array in chunks and call the function for each chunk in parallel. At the end we combine the - * results again. + * Split a big array of items in chunks and call the function for each chunk, one after + * another. At the end we combine the results again. + * + * The chunks do not run in parallel: many heavy GraphQL queries at the same time can make + * GitHub fail with timeouts or 502 errors for large releases. * * @template TItem * @template TCallbackResult @@ -345,11 +347,10 @@ export default async function success(pluginConfig, context, { Octokit }) { * @returns TCallbackResult */ async function inChunks(items, chunkSize, callback) { - const chunkCalls = []; + const results = []; for (let i = 0; i < items.length; i += chunkSize) { - chunkCalls.push(callback(items.slice(i, i + chunkSize))); + results.push(await callback(items.slice(i, i + chunkSize))); } - const results = await Promise.all(chunkCalls); return results.flat(); } diff --git a/test/success.test.js b/test/success.test.js index e88e1b70..cb9e69e1 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -1387,6 +1387,76 @@ test("Do not add comment and labels if no PR is associated with release commits" t.true(fetch.done()); }); +test("Send associatedPRs queries for large releases one chunk at a time", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITHUB_TOKEN: "github_token" }; + const options = { + branch: "master", + repositoryUrl: `https://github.com/${owner}/${repo}.git`, + }; + const commits = Array.from({ length: 250 }, (_, index) => ({ + hash: String(index).padStart(40, "0"), + message: `Commit ${index} message`, + })); + const nextRelease = { version: "1.0.0" }; + const releases = [ + { name: "GitHub release", url: "https://github.com/release" }, + ]; + + let requestsInFlight = 0; + let maxRequestsInFlight = 0; + const fetch = fetchMock + .sandbox() + .getOnce(`https://api.github.local/repos/${owner}/${repo}`, { + full_name: `${owner}/${repo}`, + clone_url: `https://api.github.local/${owner}/${repo}.git`, + }) + .post( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getAssociatedPRs("), + async () => { + requestsInFlight++; + maxRequestsInFlight = Math.max(maxRequestsInFlight, requestsInFlight); + await new Promise((resolve) => setTimeout(resolve, 10)); + requestsInFlight--; + return { data: { repository: {} } }; + }, + ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getSRIssues("), + { data: { repository: { issues: { nodes: [] } } } }, + ); + + await success( + { failTitle: "The automated release is failing 🚨" }, + { + env, + options, + commits, + nextRelease, + releases, + logger: t.context.logger, + }, + { + Octokit: TestOctokit.defaults((options) => ({ + ...options, + request: { ...options.request, fetch }, + })), + }, + ); + + const associatedPRsCalls = fetch + .calls("https://api.github.local/graphql") + .filter(([, { body }]) => body.includes("query getAssociatedPRs(")); + t.is(associatedPRsCalls.length, 3); + t.is(maxRequestsInFlight, 1); + t.true(fetch.done()); +}); + test("Do not add comment and labels if no commits is found for release", async (t) => { const owner = "test_user"; const repo = "test_repo"; From 448b85471ecfbcf9bd331061ef9976af3d5505fe Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 6 Nov 2026 11:55:18 +0100 Subject: [PATCH 2/2] fix(success): load associated PRs in two steps A single getAssociatedPRs query for 100 commits can take longer than GitHub's GraphQL timeout of about 10 seconds, even when it runs alone. The cost grows with the number of PRs for each commit, because the query loads all PR fields for each link. On backport branches, one commit can link to many PRs. Load the PRs in two steps: 1. Get only the PR numbers for each commit (50 commits per query). 2. Remove duplicates and load the full PR fields for 10 PRs per query. Also load related issues 10 per query, because they use the same PR fields. This also fixes the pagination of associated PRs, which used an undefined variable. --- lib/success.js | 110 +++++------ test/success.test.js | 446 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 444 insertions(+), 112 deletions(-) diff --git a/lib/success.js b/lib/success.js index 3cc7a352..eaa96d43 100644 --- a/lib/success.js +++ b/lib/success.js @@ -1,4 +1,12 @@ -import { isNil, uniqBy, template, flatten, isEmpty, merge } from "lodash-es"; +import { + isNil, + uniq, + uniqBy, + template, + flatten, + isEmpty, + merge, +} from "lodash-es"; import pFilter from "p-filter"; import AggregateError from "aggregate-error"; import issueParser from "issue-parser"; @@ -75,47 +83,41 @@ export default async function success(pluginConfig, context, { Octokit }) { const releaseInfos = releases.filter((release) => Boolean(release.name)); const shas = commits.map(({ hash }) => hash); - // Get associatedPRs - const associatedPRs = await inChunks(shas, 100, async (chunk) => { - const responsePRs = []; + // Get associatedPRs in two steps. Many commits share the same PRs, so we + // first get only the PR numbers and then load the full fields once per PR. + // This keeps each GraphQL query below GitHub's timeout of about 10 seconds. + const associatedPRNumbers = await inChunks(shas, 50, async (chunk) => { const { repository } = await octokit.graphql( - buildAssociatedPRsQuery(chunk), + buildAssociatedPRNumbersQuery(chunk), { owner, repo }, ); - const responseAssociatedPRs = Object.values(repository).map( - (item) => item.associatedPullRequests, - ); - for (const { nodes, pageInfo } of responseAssociatedPRs) { - if (nodes.length === 0) continue; - - responsePRs.push(...buildIssuesOrPRsFromResponseNode(nodes)); - if (pageInfo.hasNextPage) { - let cursor = pageInfo.endCursor; - let hasNextPage = true; - while (hasNextPage) { - const { repository } = await octokit.graphql( - loadSingleCommitAssociatedPRs, - { owner, repo, sha: response.commit.oid, cursor }, - ); - const { associatedPullRequests } = repository.commit; - responsePRs.push( - ...buildIssuesOrPRsFromResponseNode( - associatedPullRequests.nodes, - "PR", - ), - ); - if (associatedPullRequests.pageInfo.hasNextPage) { - cursor = associatedPullRequests.pageInfo.endCursor; - } else { - hasNextPage = false; - } - } + const numbers = []; + for (const commit of Object.values(repository)) { + let { nodes, pageInfo } = commit.associatedPullRequests; + numbers.push(...nodes.map(({ number }) => number)); + while (pageInfo.hasNextPage) { + const { repository } = await octokit.graphql( + loadSingleCommitAssociatedPRNumbers, + { owner, repo, sha: commit.oid, cursor: pageInfo.endCursor }, + ); + ({ nodes, pageInfo } = repository.commit.associatedPullRequests); + numbers.push(...nodes.map(({ number }) => number)); } } - return responsePRs; + return numbers; }); - const uniqueAssociatedPRs = uniqBy(flatten(associatedPRs), "number"); + const uniqueAssociatedPRs = await inChunks( + uniq(associatedPRNumbers), + 10, + async (chunk) => { + const { repository } = await octokit.graphql( + buildRelatedIssuesQuery(chunk), + { owner, repo }, + ); + return buildIssuesOrPRsFromResponseNode(Object.values(repository)); + }, + ); const prs = await pFilter(uniqueAssociatedPRs, async ({ number }) => { const commits = await octokit.paginate( @@ -172,7 +174,7 @@ export default async function success(pluginConfig, context, { Octokit }) { const uniqueParsedIssues = uniqBy(flatten(parsedIssues), "number"); // Get relatedIssues (or relatedPRs i.e. Issues/PRs that are closed by an associatedPR) - issues = await inChunks(uniqueParsedIssues, 100, async (chunk) => { + issues = await inChunks(uniqueParsedIssues, 10, async (chunk) => { const { repository } = await octokit.graphql( buildRelatedIssuesQuery(chunk.map((issue) => issue.number)), { owner, repo }, @@ -448,11 +450,11 @@ function buildRelatedIssuesQuery(numbers) { } /** - * Builds GraphQL query for fetching associated PRs to a list of commit hash (sha) + * Builds GraphQL query for fetching the numbers of PRs associated to a list of commit hash (sha) * @param {Array} shas * @returns {string} */ -function buildAssociatedPRsQuery(shas) { +function buildAssociatedPRNumbersQuery(shas) { return `#graphql query getAssociatedPRs($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { @@ -467,19 +469,7 @@ function buildAssociatedPRsQuery(shas) { hasNextPage } nodes { - ${baseFields} - mergeable - changedFiles - mergedAt - isDraft - mergedBy { - login - avatarUrl - url - } - commits { - totalCount - } + number } } } @@ -492,9 +482,9 @@ function buildAssociatedPRsQuery(shas) { } /** - * GraphQL Query to fetch additional associatedPR for commits that has more than 100 associatedPRs + * GraphQL Query to fetch additional associatedPR numbers for commits that has more than 100 associatedPRs */ -const loadSingleCommitAssociatedPRs = `#graphql +const loadSingleCommitAssociatedPRNumbers = `#graphql query getCommitAssociatedPRs($owner: String!, $repo: String!, $sha: String!, $cursor: String) { repository(owner: $owner, name: $repo) { commit: object(oid: $sha) { @@ -506,19 +496,7 @@ const loadSingleCommitAssociatedPRs = `#graphql hasNextPage } nodes { - ${baseFields} - mergeable - changedFiles - mergedAt - isDraft - mergedBy { - login - avatarUrl - url - } - commits { - totalCount - } + number } } } diff --git a/test/success.test.js b/test/success.test.js index cb9e69e1..8f6bd19c 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -95,6 +95,19 @@ test("Add comment and labels to PRs associated with release commits and issues s }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + }, + }, + }, + ) .postOnce( (url, { body }) => url === "https://api.github.local/graphql" && @@ -341,48 +354,62 @@ test("Add comment and labels to PRs associated with release commits and issues ( .getOnce(`https://api.github.local/repos/${owner}/${repo}`, { full_name: `${owner}/${repo}`, }) - .postOnce("https://api.github.local/graphql", { - data: { - repository: { - commit123: { - oid: "123", - associatedPullRequests: { - pageInfo: { - endCursor: "YE", - hasNextPage: true, + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getAssociatedPRs("), + { + data: { + repository: { + commit123: { + oid: "123", + associatedPullRequests: { + pageInfo: { + endCursor: "YE", + hasNextPage: true, + }, + nodes: [prs[0]], }, - nodes: [prs[0]], }, - }, - commit456: { - oid: "456", - associatedPullRequests: { - pageInfo: { - endCursor: "NI", - hasNextPage: false, + commit456: { + oid: "456", + associatedPullRequests: { + pageInfo: { + endCursor: "NI", + hasNextPage: false, + }, + nodes: [prs[1]], }, - nodes: [prs[1]], }, - }, - commit789: { - oid: "789", - associatedPullRequests: { - pageInfo: { - endCursor: "NI", - hasNextPage: false, + commit789: { + oid: "789", + associatedPullRequests: { + pageInfo: { + endCursor: "NI", + hasNextPage: false, + }, + nodes: [prs[2]], }, - nodes: [prs[2]], }, }, }, }, - }) + ) .postOnce( - "https://api.github.local/graphql", + (url, { body }) => { + if (url !== "https://api.github.local/graphql") return false; + const { query, variables } = JSON.parse(body); + return ( + query.includes("query getCommitAssociatedPRs(") && + variables.sha === "123" && + variables.cursor === "YE" + ); + }, { data: { repository: { commit: { + oid: "123", associatedPullRequests: { pageInfo: { endCursor: "NE", @@ -394,8 +421,20 @@ test("Add comment and labels to PRs associated with release commits and issues ( }, }, }, + ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), { - overwriteRoutes: true, + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + pr2: prs[2], + pr3: prs[3], + }, + }, }, ) .postOnce( @@ -483,10 +522,49 @@ test("Add comment and labels to PRs associated with release commits and issues ( }, }, ) + .getOnce( + `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, + [{ sha: commits[0].hash }], + ) + .getOnce( + `https://api.github.local/repos/${owner}/${repo}/pulls/2/commits`, + [{ sha: commits[1].hash }], + ) + .getOnce( + `https://api.github.local/repos/${owner}/${repo}/pulls/5/commits`, + [{ sha: commits[2].hash }], + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/6/commits`, [{ sha: commits[0].hash }], ) + .postOnce( + `https://api.github.local/repos/${owner}/${repo}/issues/1/comments`, + { html_url: "https://github.com/successcomment-1" }, + ) + .postOnce( + `https://api.github.local/repos/${owner}/${repo}/issues/1/labels`, + {}, + { body: ["released"] }, + ) + .postOnce( + `https://api.github.local/repos/${owner}/${repo}/issues/2/comments`, + { html_url: "https://github.com/successcomment-2" }, + ) + .postOnce( + `https://api.github.local/repos/${owner}/${repo}/issues/2/labels`, + {}, + { body: ["released"] }, + ) + .postOnce( + `https://api.github.local/repos/${owner}/${repo}/issues/5/comments`, + { html_url: "https://github.com/successcomment-5" }, + ) + .postOnce( + `https://api.github.local/repos/${owner}/${repo}/issues/5/labels`, + {}, + { body: ["released"] }, + ) .postOnce( `https://api.github.local/repos/${owner}/${repo}/issues/3/comments`, { html_url: "https://github.com/successcomment-3" }, @@ -567,16 +645,23 @@ test("Add comment and labels to PRs associated with release commits and issues ( t.true( t.context.log.calledWith("Added labels %O to issue #%d", ["released"], 4), ); - t.true( - t.context.log.calledWith( - "Added comment to PR #%d: %s", - 6, - "https://github.com/successcomment-6", - ), - ); - t.true( - t.context.log.calledWith("Added labels %O to PR #%d", ["released"], 6), - ); + // PR 6 is only on the second page of associatedPullRequests for commit 123 + for (const number of [1, 2, 5, 6]) { + t.true( + t.context.log.calledWith( + "Added comment to PR #%d: %s", + number, + `https://github.com/successcomment-${number}`, + ), + ); + t.true( + t.context.log.calledWith( + "Added labels %O to PR #%d", + ["released"], + number, + ), + ); + } t.true(fetch.done()); }); @@ -647,6 +732,19 @@ test("Add comment and labels to PRs associated with release commits and issues c }, }, ) + .postOnce( + (url, { body }) => + url === "https://custom-url.com/prefix/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + }, + }, + }, + ) .postOnce( (url, { body }) => url === "https://custom-url.com/prefix/graphql" && @@ -987,6 +1085,23 @@ test("Make multiple search queries if necessary", async (t) => { }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + pr2: prs[2], + pr3: prs[3], + pr4: prs[4], + pr5: prs[5], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -1235,6 +1350,19 @@ test("Do not add comment and labels for unrelated PR returned by search (compare }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: "rebased_sha" }], @@ -1387,7 +1515,7 @@ test("Do not add comment and labels if no PR is associated with release commits" t.true(fetch.done()); }); -test("Send associatedPRs queries for large releases one chunk at a time", async (t) => { +test("Load associatedPRs of large releases in small, sequential GraphQL queries", async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GITHUB_TOKEN: "github_token" }; @@ -1395,10 +1523,13 @@ test("Send associatedPRs queries for large releases one chunk at a time", async branch: "master", repositoryUrl: `https://github.com/${owner}/${repo}.git`, }; - const commits = Array.from({ length: 250 }, (_, index) => ({ + // All commits belong to the same PR, as in a release that merges a big + // backport branch. + const commits = Array.from({ length: 120 }, (_, index) => ({ hash: String(index).padStart(40, "0"), message: `Commit ${index} message`, })); + const pr = { number: 1, __typename: "PullRequest", state: "closed" }; const nextRelease = { version: "1.0.0" }; const releases = [ { name: "GitHub release", url: "https://github.com/release" }, @@ -1416,14 +1547,49 @@ test("Send associatedPRs queries for large releases one chunk at a time", async (url, { body }) => url === "https://api.github.local/graphql" && JSON.parse(body).query.includes("query getAssociatedPRs("), - async () => { + async (url, { body }) => { requestsInFlight++; maxRequestsInFlight = Math.max(maxRequestsInFlight, requestsInFlight); await new Promise((resolve) => setTimeout(resolve, 10)); requestsInFlight--; - return { data: { repository: {} } }; + + const shas = [...body.matchAll(/object\(oid: \\"(\w+)\\"\)/g)].map( + ([, sha]) => sha, + ); + const repository = Object.fromEntries( + shas.map((sha) => [ + `commit${sha.slice(0, 12)}`, + { + oid: sha, + associatedPullRequests: { + pageInfo: { endCursor: "NI", hasNextPage: false }, + nodes: [{ number: pr.number }], + }, + }, + ]), + ); + return { data: { repository } }; }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { data: { repository: { issue1: pr } } }, + ) + .getOnce( + `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, + [{ sha: commits[0].hash }], + ) + .postOnce( + `https://api.github.local/repos/${owner}/${repo}/issues/1/comments`, + { html_url: "https://github.com/successcomment-1" }, + ) + .postOnce( + `https://api.github.local/repos/${owner}/${repo}/issues/1/labels`, + {}, + { body: ["released"] }, + ) .postOnce( (url, { body }) => url === "https://api.github.local/graphql" && @@ -1449,11 +1615,25 @@ test("Send associatedPRs queries for large releases one chunk at a time", async }, ); - const associatedPRsCalls = fetch + const graphqlQueries = fetch .calls("https://api.github.local/graphql") - .filter(([, { body }]) => body.includes("query getAssociatedPRs(")); - t.is(associatedPRsCalls.length, 3); + .map(([, { body }]) => JSON.parse(body).query); + const associatedPRsQueries = graphqlQueries.filter((query) => + query.includes("query getAssociatedPRs("), + ); + const prDetailsQueries = graphqlQueries.filter((query) => + query.includes("query getRelatedIssues("), + ); + t.is(associatedPRsQueries.length, 3); t.is(maxRequestsInFlight, 1); + t.is(prDetailsQueries.length, 1); + t.true( + t.context.log.calledWith( + "Added comment to PR #%d: %s", + 1, + "https://github.com/successcomment-1", + ), + ); t.true(fetch.done()); }); @@ -1750,6 +1930,20 @@ test("Ignore missing and forbidden issues/PRs", async (t) => { }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + pr2: prs[2], + }, + }, + }, + ) .postOnce( (url, { body }) => url === "https://api.github.local/graphql" && @@ -2050,6 +2244,18 @@ test("Add custom comment and labels", async (t) => { }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -2162,6 +2368,18 @@ test("Add custom label", async (t) => { }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -2267,6 +2485,18 @@ test("Comment on issue/PR without ading a label", async (t) => { }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -2379,6 +2609,18 @@ test("Editing the release to include all release links at the bottom", async (t) }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -2502,6 +2744,18 @@ test("Editing the release to include all release links at the top", async (t) => }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -2622,6 +2876,18 @@ test("Editing the release to include all release links with no additional releas }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -2731,6 +2997,18 @@ test("Editing the release to include all release links with no additional releas }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -2833,6 +3111,18 @@ test("Editing the release to include all release links with no releases", async }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -2937,6 +3227,18 @@ test("Editing the release with no ID in the release", async (t) => { }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -3056,6 +3358,19 @@ test("Ignore errors when adding comments and closing issues", async (t) => { }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/1/commits`, [{ sha: commits[0].hash }], @@ -3570,6 +3885,19 @@ test('Add comment and label to found issues/associatedPR using the "successComme }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + }, + }, + }, + ) .getOnce( `https://api.github.local/repos/${owner}/${repo}/pulls/4/commits`, [{ sha: commits[0].hash }], @@ -3811,6 +4139,19 @@ test('Does not comment/label associatedPR and relatedIssues created by "Bots"', }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + }, + }, + }, + ) .postOnce( (url, { body }) => url === "https://api.github.local/graphql" && @@ -4057,6 +4398,19 @@ test('Does not comment/label "associatedPR" when "successCommentCondition" disab }, }, ) + .postOnce( + (url, { body }) => + url === "https://api.github.local/graphql" && + JSON.parse(body).query.includes("query getRelatedIssues("), + { + data: { + repository: { + pr0: prs[0], + pr1: prs[1], + }, + }, + }, + ) .postOnce( (url, { body }) => url === "https://api.github.local/graphql" &&