aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap354
-rw-r--r--lib/platform/bitbucket-server/index.spec.ts97
-rw-r--r--lib/platform/bitbucket-server/index.ts39
-rw-r--r--lib/platform/bitbucket-server/utils.ts39
4 files changed, 519 insertions, 10 deletions
diff --git a/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap b/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap
index 4b75af9a009..ee6f150c134 100644
--- a/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap
+++ b/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap
@@ -396,6 +396,76 @@ Array [
]
`;
+exports[`platform/bitbucket-server/index endpoint with no path addReviewers throws on invalid reviewers 1`] = `"Response code 409 (Conflict)"`;
+
+exports[`platform/bitbucket-server/index endpoint with no path addReviewers throws on invalid reviewers 2`] = `
+Array [
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/branches/default",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge",
+ },
+ Object {
+ "body": "{\\"title\\":\\"title\\",\\"version\\":1,\\"reviewers\\":[{\\"user\\":{\\"name\\":\\"userName2\\"}},{\\"user\\":{\\"name\\":\\"name\\"}}]}",
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "content-length": "98",
+ "content-type": "application/json",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "PUT",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+]
+`;
+
exports[`platform/bitbucket-server/index endpoint with no path addReviewers throws repository-changed 1`] = `
Array [
Object {
@@ -3417,6 +3487,113 @@ Array [
]
`;
+exports[`platform/bitbucket-server/index endpoint with no path updatePr() handles invalid users gracefully by retrying without invalid reviewers 1`] = `
+Array [
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/branches/default",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge",
+ },
+ Object {
+ "body": "{\\"title\\":\\"title\\",\\"description\\":\\"body\\",\\"version\\":1,\\"reviewers\\":[{\\"user\\":{\\"name\\":\\"userName2\\"}}]}",
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "content-length": "94",
+ "content-type": "application/json",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "PUT",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge",
+ },
+ Object {
+ "body": "{\\"title\\":\\"title\\",\\"description\\":\\"body\\",\\"version\\":1,\\"reviewers\\":[]}",
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "content-length": "65",
+ "content-type": "application/json",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "PUT",
+ "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+]
+`;
+
exports[`platform/bitbucket-server/index endpoint with no path updatePr() puts PR 1`] = `
Array [
Object {
@@ -4237,6 +4414,76 @@ Array [
]
`;
+exports[`platform/bitbucket-server/index endpoint with path addReviewers throws on invalid reviewers 1`] = `"Response code 409 (Conflict)"`;
+
+exports[`platform/bitbucket-server/index endpoint with path addReviewers throws on invalid reviewers 2`] = `
+Array [
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/branches/default",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge",
+ },
+ Object {
+ "body": "{\\"title\\":\\"title\\",\\"version\\":1,\\"reviewers\\":[{\\"user\\":{\\"name\\":\\"userName2\\"}},{\\"user\\":{\\"name\\":\\"name\\"}}]}",
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "content-length": "98",
+ "content-type": "application/json",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "PUT",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+]
+`;
+
exports[`platform/bitbucket-server/index endpoint with path addReviewers throws repository-changed 1`] = `
Array [
Object {
@@ -7258,6 +7505,113 @@ Array [
]
`;
+exports[`platform/bitbucket-server/index endpoint with path updatePr() handles invalid users gracefully by retrying without invalid reviewers 1`] = `
+Array [
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/branches/default",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge",
+ },
+ Object {
+ "body": "{\\"title\\":\\"title\\",\\"description\\":\\"body\\",\\"version\\":1,\\"reviewers\\":[{\\"user\\":{\\"name\\":\\"userName2\\"}}]}",
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "content-length": "94",
+ "content-type": "application/json",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "PUT",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+ Object {
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "GET",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge",
+ },
+ Object {
+ "body": "{\\"title\\":\\"title\\",\\"description\\":\\"body\\",\\"version\\":1,\\"reviewers\\":[]}",
+ "headers": Object {
+ "accept": "application/json",
+ "accept-encoding": "gzip, deflate",
+ "authorization": "Basic YWJjOjEyMw==",
+ "content-length": "65",
+ "content-type": "application/json",
+ "host": "stash.renovatebot.com",
+ "user-agent": "https://github.com/renovatebot/renovate",
+ "x-atlassian-token": "no-check",
+ },
+ "method": "PUT",
+ "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5",
+ },
+]
+`;
+
exports[`platform/bitbucket-server/index endpoint with path updatePr() puts PR 1`] = `
Array [
Object {
diff --git a/lib/platform/bitbucket-server/index.spec.ts b/lib/platform/bitbucket-server/index.spec.ts
index 6689a5b1324..925c68fd486 100644
--- a/lib/platform/bitbucket-server/index.spec.ts
+++ b/lib/platform/bitbucket-server/index.spec.ts
@@ -506,6 +506,46 @@ describe(getName(__filename), () => {
expect(httpMock.getTrace()).toMatchSnapshot();
});
+ it('throws on invalid reviewers', async () => {
+ const scope = await initRepo();
+ scope
+ .get(
+ `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
+ )
+ .reply(200, prMock(url, 'SOME', 'repo'))
+ .get(
+ `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge`
+ )
+ .reply(200, { conflicted: false })
+ .put(
+ `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
+ )
+ .reply(409, {
+ errors: [
+ {
+ context: 'reviewers',
+ message:
+ 'Errors encountered while adding some reviewers to this pull request.',
+ exceptionName:
+ 'com.atlassian.bitbucket.pull.InvalidPullRequestReviewersException',
+ reviewerErrors: [
+ {
+ context: 'name',
+ message: 'name is not a user.',
+ exceptionName: null,
+ },
+ ],
+ validReviewers: [],
+ },
+ ],
+ });
+
+ await expect(
+ bitbucket.addReviewers(5, ['name'])
+ ).rejects.toThrowErrorMatchingSnapshot();
+ expect(httpMock.getTrace()).toMatchSnapshot();
+ });
+
it('throws', async () => {
const scope = await initRepo();
scope
@@ -1348,6 +1388,63 @@ describe(getName(__filename), () => {
expect(httpMock.getTrace()).toMatchSnapshot();
});
+ it('handles invalid users gracefully by retrying without invalid reviewers', async () => {
+ const scope = await initRepo();
+ scope
+ .get(
+ `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
+ )
+ .reply(200, prMock(url, 'SOME', 'repo'))
+ .get(
+ `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge`
+ )
+ .reply(200, { conflicted: false })
+ .put(
+ `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
+ )
+ .reply(409, {
+ errors: [
+ {
+ context: 'reviewers',
+ message:
+ 'Errors encountered while adding some reviewers to this pull request.',
+ exceptionName:
+ 'com.atlassian.bitbucket.pull.InvalidPullRequestReviewersException',
+ reviewerErrors: [
+ {
+ context: 'userName2',
+ message: 'userName2 is not a user.',
+ exceptionName: null,
+ },
+ ],
+ validReviewers: [],
+ },
+ ],
+ })
+ .get(
+ `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
+ )
+ .reply(200, prMock(url, 'SOME', 'repo'))
+ .get(
+ `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge`
+ )
+ .reply(200, { conflicted: false })
+ .put(
+ `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`,
+ (body) => body.reviewers.length === 0
+ )
+ .reply(200, prMock(url, 'SOME', 'repo'));
+
+ await bitbucket.updatePr({
+ number: 5,
+ prTitle: 'title',
+ prBody: 'body',
+ state: PrState.Open,
+ });
+
+ expect(httpMock.getTrace()).toMatchSnapshot();
+ });
+
it('throws repository-changed', async () => {
const scope = await initRepo();
scope
diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts
index 5066ac9f048..609255bf32d 100644
--- a/lib/platform/bitbucket-server/index.ts
+++ b/lib/platform/bitbucket-server/index.ts
@@ -128,7 +128,7 @@ export async function getJsonFile(fileName: string): Promise<any | null> {
return null;
}
-// Initialize GitLab by getting base branch
+// Initialize BitBucket Server by getting base branch
export async function initRepo({
repository,
localDir,
@@ -584,15 +584,15 @@ export async function addReviewers(
);
await getPr(prNo, true);
} catch (err) {
+ logger.warn({ err, reviewers, prNo }, `Failed to add reviewers`);
if (err.statusCode === 404) {
throw new Error(REPOSITORY_NOT_FOUND);
- } else if (err.statusCode === 409) {
+ } else if (
+ err.statusCode === 409 &&
+ !utils.isInvalidReviewersResponse(err)
+ ) {
throw new Error(REPOSITORY_CHANGED);
} else {
- logger.fatal(
- { err },
- `Failed to add reviewers '${reviewers.join(', ')}' to #${prNo}`
- );
throw err;
}
}
@@ -850,7 +850,10 @@ export async function updatePr({
prTitle: title,
prBody: rawDescription,
state,
-}: UpdatePrConfig): Promise<void> {
+ bitbucketInvalidReviewers,
+}: UpdatePrConfig & {
+ bitbucketInvalidReviewers: string[] | undefined;
+}): Promise<void> {
const description = sanitize(rawDescription);
logger.debug(`updatePr(${prNo}, title=${title})`);
@@ -870,7 +873,11 @@ export async function updatePr({
title,
description,
version: pr.version,
- reviewers: pr.reviewers.map((name: string) => ({ user: { name } })),
+ reviewers: pr.reviewers
+ .filter(
+ (name: string) => !bitbucketInvalidReviewers?.includes(name)
+ )
+ .map((name: string) => ({ user: { name } })),
},
}
);
@@ -898,12 +905,24 @@ export async function updatePr({
updatePrVersion(pr.number, updatedStatePr.version);
}
} catch (err) {
+ logger.debug({ err, prNo }, `Failed to update PR`);
if (err.statusCode === 404) {
throw new Error(REPOSITORY_NOT_FOUND);
} else if (err.statusCode === 409) {
- throw new Error(REPOSITORY_CHANGED);
+ if (utils.isInvalidReviewersResponse(err) && !bitbucketInvalidReviewers) {
+ // Retry again with invalid reviewers being removed
+ const invalidReviewers = utils.getInvalidReviewers(err);
+ await updatePr({
+ number: prNo,
+ prTitle: title,
+ prBody: rawDescription,
+ state,
+ bitbucketInvalidReviewers: invalidReviewers,
+ });
+ } else {
+ throw new Error(REPOSITORY_CHANGED);
+ }
} else {
- logger.fatal({ err }, `Failed to update PR`);
throw err;
}
}
diff --git a/lib/platform/bitbucket-server/utils.ts b/lib/platform/bitbucket-server/utils.ts
index 9c438dbf0bf..0819ce062cf 100644
--- a/lib/platform/bitbucket-server/utils.ts
+++ b/lib/platform/bitbucket-server/utils.ts
@@ -1,10 +1,14 @@
// SEE for the reference https://github.com/renovatebot/renovate/blob/c3e9e572b225085448d94aa121c7ec81c14d3955/lib/platform/bitbucket/utils.js
import url from 'url';
+import { HTTPError, Response } from 'got';
import { PrState } from '../../types';
import { HttpOptions, HttpPostOptions, HttpResponse } from '../../util/http';
import { BitbucketServerHttp } from '../../util/http/bitbucket-server';
import { BbbsRestPr, BbsPr } from './types';
+const BITBUCKET_INVALID_REVIEWERS_EXCEPTION =
+ 'com.atlassian.bitbucket.pull.InvalidPullRequestReviewersException';
+
const bitbucketServerHttp = new BitbucketServerHttp();
// https://docs.atlassian.com/bitbucket-server/rest/6.0.0/bitbucket-rest.html#idp250
@@ -118,3 +122,38 @@ export interface BitbucketStatus {
key: string;
state: BitbucketBranchState;
}
+
+interface BitbucketErrorResponse {
+ errors?: {
+ exceptionName?: string;
+ reviewerErrors?: { context?: string }[];
+ }[];
+}
+
+interface BitbucketError extends HTTPError {
+ readonly response: Response<BitbucketErrorResponse>;
+}
+
+export function isInvalidReviewersResponse(err: BitbucketError): boolean {
+ const errors = err?.response?.body?.errors || [];
+ return (
+ errors.length > 0 &&
+ errors.every(
+ (error) => error.exceptionName === BITBUCKET_INVALID_REVIEWERS_EXCEPTION
+ )
+ );
+}
+
+export function getInvalidReviewers(err: BitbucketError): string[] {
+ const errors = err?.response?.body?.errors || [];
+ let invalidReviewers = [];
+ for (const error of errors) {
+ if (error.exceptionName === BITBUCKET_INVALID_REVIEWERS_EXCEPTION) {
+ invalidReviewers = invalidReviewers.concat(
+ error.reviewerErrors?.map(({ context }) => context) || []
+ );
+ }
+ }
+
+ return invalidReviewers;
+}