From 3ee48571b0aa39d81ce13fb44f01404a7f6a3bc4 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Wed, 18 Dec 2024 10:43:53 +0530 Subject: [PATCH] fix(platform/bitbucket): ensure `getPrList()` runtime integrity (#32970) --- lib/modules/platform/bitbucket/index.spec.ts | 137 ++++++++++++++++++- lib/modules/platform/bitbucket/index.ts | 39 ++++-- 2 files changed, 158 insertions(+), 18 deletions(-) diff --git a/lib/modules/platform/bitbucket/index.spec.ts b/lib/modules/platform/bitbucket/index.spec.ts index 86b482f09851da..a4f372e15e39b6 100644 --- a/lib/modules/platform/bitbucket/index.spec.ts +++ b/lib/modules/platform/bitbucket/index.spec.ts @@ -1668,7 +1668,12 @@ describe('modules/platform/bitbucket/index', () => { .get('/2.0/repositories/some/repo/pullrequests/5') .reply(200, { reviewers: [reviewer] }) .put('/2.0/repositories/some/repo/pullrequests/5') - .reply(200); + .reply(200, { id: 5 }) + .get(`/2.0/repositories/some/repo/pullrequests`) + .query(true) + .reply(200, { + values: [{ id: 5 }], + }); await expect( bitbucket.updatePr({ number: 5, @@ -1736,7 +1741,12 @@ describe('modules/platform/bitbucket/index', () => { account_status: 'inactive', }) .put('/2.0/repositories/some/repo/pullrequests/5') - .reply(200); + .reply(200, { id: 5 }) + .get(`/2.0/repositories/some/repo/pullrequests`) + .query(true) + .reply(200, { + values: [{ id: 5 }], + }); await expect( bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' }), ).toResolve(); @@ -1779,7 +1789,12 @@ describe('modules/platform/bitbucket/index', () => { ) .reply(200) .put('/2.0/repositories/some/repo/pullrequests/5') - .reply(200); + .reply(200, { id: 5 }) + .get(`/2.0/repositories/some/repo/pullrequests`) + .query(true) + .reply(200, { + values: [{ id: 5 }], + }); await expect( bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' }), @@ -1884,9 +1899,14 @@ describe('modules/platform/bitbucket/index', () => { .get('/2.0/repositories/some/repo/pullrequests/5') .reply(200, { values: [pr] }) .put('/2.0/repositories/some/repo/pullrequests/5') - .reply(200) + .reply(200, { id: 5 }) .post('/2.0/repositories/some/repo/pullrequests/5/decline') - .reply(200); + .reply(200) + .get(`/2.0/repositories/some/repo/pullrequests`) + .query(true) + .reply(200, { + values: [{ id: 5 }], + }); expect( await bitbucket.updatePr({ @@ -1898,6 +1918,113 @@ describe('modules/platform/bitbucket/index', () => { }); }); + describe('maintains pr cache integrity at runtime', () => { + it('pr cache gets updated after a pr is created', async () => { + const projectReviewer = { + type: 'default_reviewer', + reviewer_type: 'project', + user: { + display_name: 'Bob Smith', + uuid: '{d2238482-2e9f-48b3-8630-de22ccb9e42f}', + account_id: '123', + }, + }; + const repoReviewer = { + type: 'default_reviewer', + reviewer_type: 'repository', + user: { + display_name: 'Jane Smith', + uuid: '{90b6646d-1724-4a64-9fd9-539515fe94e9}', + account_id: '456', + }, + }; + + const scope = httpMock.scope(baseUrl); + scope.get('/2.0/user').reply(200, { uuid: '12345' }); + await bitbucket.initPlatform({ username: 'renovate', password: 'pass' }); + await initRepoMock(undefined, null, scope); + scope + .get(`/2.0/repositories/some/repo/pullrequests`) + .query(true) + .reply(200, { + values: [ + { + id: 1, + author: { uuid: '12345' }, + source: { branch: { name: 'branch-a' } }, + destination: { branch: { name: 'branch-b' } }, + state: 'OPEN', + }, + ], + }) + .get( + '/2.0/repositories/some/repo/effective-default-reviewers?pagelen=100', + ) + .reply(200, { + values: [projectReviewer, repoReviewer], + }) + .post('/2.0/repositories/some/repo/pullrequests') + .reply(200, { id: 5 }); + + await bitbucket.getPrList(); // cache is now initialized + + await bitbucket.createPr({ + sourceBranch: 'branch', + targetBranch: 'master', + prTitle: 'title', + prBody: 'body', + platformPrOptions: { + bbUseDefaultReviewers: true, + }, + }); + + const newPrList = await bitbucket.getPrList(); + expect(newPrList).toHaveLength(2); + }); + + it('pr cache gets updated after a pr is updated', async () => { + const reviewer = { + display_name: 'Jane Smith', + uuid: '{90b6646d-1724-4a64-9fd9-539515fe94e9}', + }; + const scope = httpMock.scope(baseUrl); + scope.get('/2.0/user').reply(200, { uuid: '12345' }); + await bitbucket.initPlatform({ username: 'renovate', password: 'pass' }); + await initRepoMock(undefined, null, scope); + scope + .get(`/2.0/repositories/some/repo/pullrequests`) + .query(true) + .reply(200, { + values: [ + { + id: 5, + author: { uuid: '12345' }, + source: { branch: { name: 'branch-a' } }, + destination: { branch: { name: 'branch-b' } }, + state: 'OPEN', + title: 'title', + }, + ], + }) + .get('/2.0/repositories/some/repo/pullrequests/5') + .reply(200, { reviewers: [reviewer] }) + .put('/2.0/repositories/some/repo/pullrequests/5') + .reply(200, { id: 5, title: 'newTitle' }); + + const oldPrList = await bitbucket.getPrList(); // cache is now initialized + expect(oldPrList.find((pr) => pr.title === 'title')).toBeDefined(); + await bitbucket.updatePr({ + number: 5, + prTitle: 'newTitle', + prBody: 'body', + targetBranch: 'new_base', + }); + + const newPrList = await bitbucket.getPrList(); + expect(newPrList.find((pr) => pr.title === 'newTitle')).toBeDefined(); + }); + }); + describe('mergePr()', () => { it('posts Merge with optional merge strategy', async () => { const scope = await initRepoMock(); diff --git a/lib/modules/platform/bitbucket/index.ts b/lib/modules/platform/bitbucket/index.ts index 35d125ffda89d7..11594cd7bc24b8 100644 --- a/lib/modules/platform/bitbucket/index.ts +++ b/lib/modules/platform/bitbucket/index.ts @@ -1022,6 +1022,7 @@ export async function updatePr({ ) ).body; + let updatedPrRes: PrResponse; try { const body: any = { title, @@ -1036,10 +1037,12 @@ export async function updatePr({ }; } - await bitbucketHttp.putJson( - `/2.0/repositories/${config.repository}/pullrequests/${prNo}`, - { body }, - ); + updatedPrRes = ( + await bitbucketHttp.putJson( + `/2.0/repositories/${config.repository}/pullrequests/${prNo}`, + { body }, + ) + ).body; } catch (err) { // Try sanitizing reviewers const sanitizedReviewers = await sanitizeReviewers(pr.reviewers, err); @@ -1047,16 +1050,18 @@ export async function updatePr({ if (sanitizedReviewers === undefined) { throw err; } else { - await bitbucketHttp.putJson( - `/2.0/repositories/${config.repository}/pullrequests/${prNo}`, - { - body: { - title, - description: sanitize(description), - reviewers: sanitizedReviewers, + updatedPrRes = ( + await bitbucketHttp.putJson( + `/2.0/repositories/${config.repository}/pullrequests/${prNo}`, + { + body: { + title, + description: sanitize(description), + reviewers: sanitizedReviewers, + }, }, - }, - ); + ) + ).body; } } @@ -1065,6 +1070,14 @@ export async function updatePr({ `/2.0/repositories/${config.repository}/pullrequests/${prNo}/decline`, ); } + + // update pr cache + await BitbucketPrCache.setPr( + bitbucketHttp, + config.repository, + renovateUserUuid, + utils.prInfo({ ...updatedPrRes, ...(state && { state }) }), + ); } export async function mergePr({