Skip to content
Snippets Groups Projects
Select Git revision
  • 15d6d3502ee7c5dd87db0997ce2d360bca43f2b5
  • main default protected
  • renovate/main-zod-3.x
  • renovate/main-ghcr.io-renovatebot-base-image-10.x
  • renovate/main-ghcr.io-containerbase-devcontainer-13.x
  • next
  • revert-31645-feat/rename-gradle-wrapper-validation-action
  • renovate/main-redis-5.x
  • fix/36615b-branch-reuse-no-cache
  • chore/punycode
  • fix/36615-branch-reuse-bug
  • refactor/pin-new-value
  • feat/36219--git-x509-signing
  • feat/structured-logger
  • hotfix/39.264.1
  • feat/skip-dangling
  • gh-readonly-queue/next/pr-36034-7a061c4ca1024a19e2c295d773d9642625d1c2be
  • hotfix/39.238.3
  • refactor/gitlab-auto-approve
  • feat/template-strings
  • gh-readonly-queue/next/pr-35654-137d934242c784e0c45d4b957362214f0eade1d7
  • 41.28.2
  • 41.28.1
  • 41.28.0
  • 41.27.1
  • 41.27.0
  • 41.26.2
  • 41.26.1
  • 41.26.0
  • 41.25.1
  • 41.25.0
  • 41.24.0
  • 41.23.5
  • 41.23.4
  • 41.23.3
  • 41.23.2
  • 41.23.1
  • 41.23.0
  • 41.22.0
  • 41.21.4
  • 41.21.3
41 results

gh-got-wrapper.js

Blame
  • index.spec.ts 66.87 KiB
    import nock from 'nock';
    import * as httpMock from '../../../test/httpMock';
    import {
      REPOSITORY_CHANGED,
      REPOSITORY_DISABLED,
      REPOSITORY_NOT_FOUND,
    } from '../../constants/error-messages';
    import { PR_STATE_CLOSED, PR_STATE_OPEN } from '../../constants/pull-requests';
    import { BranchStatus } from '../../types';
    import * as _git from '../../util/git';
    import { Platform } from '../common';
    
    function repoMock(
      endpoint: URL | string,
      projectKey: string,
      repositorySlug: string
    ) {
      const projectKeyLower = projectKey.toLowerCase();
      return {
        slug: repositorySlug,
        id: 13076,
        name: repositorySlug,
        scmId: 'git',
        state: 'AVAILABLE',
        statusMessage: 'Available',
        forkable: true,
        project: {
          key: projectKey,
          id: 2900,
          name: `${repositorySlug}'s name`,
          public: false,
          type: 'NORMAL',
          links: {
            self: [
              { href: `https://stash.renovatebot.com/projects/${projectKey}` },
            ],
          },
        },
        public: false,
        links: {
          clone: [
            {
              href: `${endpoint}/scm/${projectKeyLower}/${repositorySlug}.git`,
              name: 'http',
            },
            {
              href: `ssh://git@stash.renovatebot.com:7999/${projectKeyLower}/${repositorySlug}.git`,
              name: 'ssh',
            },
          ],
          self: [
            {
              href: `${endpoint}/projects/${projectKey}/repos/${repositorySlug}/browse`,
            },
          ],
        },
      };
    }
    
    function prMock(endpoint, projectKey, repositorySlug) {
      return {
        id: 5,
        version: 1,
        title: 'title',
        description: '* Line 1\r\n* Line 2',
        state: 'OPEN',
        open: true,
        closed: false,
        createdDate: 1547853840016,
        updatedDate: 1547853840016,
        fromRef: {
          id: 'refs/heads/userName1/pullRequest5',
          displayId: 'userName1/pullRequest5',
          latestCommit: '55efc02b2ab13a43a66cf705f5faacfcc6a762b4',
          // Removed this with the idea it's not needed
          // repository: {},
        },
        toRef: {
          id: 'refs/heads/master',
          displayId: 'master',
          latestCommit: '0d9c7726c3d628b7e28af234595cfd20febdbf8e',
          // Removed this with the idea it's not needed
          // repository: {},
        },
        locked: false,
        author: {
          user: {
            name: 'userName1',
            emailAddress: 'userName1@renovatebot.com',
            id: 144846,
            displayName: 'Renovate Bot',
            active: true,
            slug: 'userName1',
            type: 'NORMAL',
            links: {
              self: [{ href: `${endpoint}/users/userName1` }],
            },
          },
          role: 'AUTHOR',
          approved: false,
          status: 'UNAPPROVED',
        },
        reviewers: [
          {
            user: {
              name: 'userName2',
              emailAddress: 'userName2@renovatebot.com',
              id: 71155,
              displayName: 'Renovate bot 2',
              active: true,
              slug: 'userName2',
              type: 'NORMAL',
              links: {
                self: [{ href: `${endpoint}/users/userName2` }],
              },
            },
            role: 'REVIEWER',
            approved: false,
            status: 'UNAPPROVED',
          },
        ],
        participants: [],
        links: {
          self: [
            {
              href: `${endpoint}/projects/${projectKey}/repos/${repositorySlug}/pull-requests/5`,
            },
          ],
        },
      };
    }
    
    const scenarios = {
      'endpoint with no path': new URL('https://stash.renovatebot.com'),
      'endpoint with path': new URL('https://stash.renovatebot.com/vcs'),
    };
    
    describe('platform/bitbucket-server', () => {
      Object.entries(scenarios).forEach(([scenarioName, url]) => {
        const urlHost = url.origin;
        const urlPath = url.pathname === '/' ? '' : url.pathname;
    
        describe(scenarioName, () => {
          let bitbucket: Platform;
          let hostRules: jest.Mocked<typeof import('../../util/host-rules')>;
          let git: jest.Mocked<typeof _git>;
    
          async function initRepo(config = {}): Promise<nock.Scope> {
            const scope = httpMock
              .scope(urlHost)
              .get(`${urlPath}/rest/api/1.0/projects/SOME/repos/repo`)
              .reply(200, repoMock(url, 'SOME', 'repo'))
              .get(
                `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/branches/default`
              )
              .reply(200, {
                displayId: 'master',
              });
            await bitbucket.initRepo({
              endpoint: 'https://stash.renovatebot.com/vcs/',
              repository: 'SOME/repo',
              localDir: '',
              optimizeForDisabled: false,
              ...config,
            });
            return scope;
          }
    
          beforeEach(async () => {
            // reset module
            jest.resetModules();
            httpMock.reset();
            httpMock.setup();
            jest.mock('delay');
            jest.mock('../../util/git');
            jest.mock('../../util/host-rules');
            hostRules = require('../../util/host-rules');
            bitbucket = await import('.');
            git = require('../../util/git');
            git.branchExists.mockResolvedValue(true);
            git.isBranchStale.mockResolvedValue(false);
            git.getBranchCommit.mockResolvedValue(
              '0d9c7726c3d628b7e28af234595cfd20febdbf8e'
            );
            const endpoint =
              scenarioName === 'endpoint with path'
                ? 'https://stash.renovatebot.com/vcs/'
                : 'https://stash.renovatebot.com';
            hostRules.find.mockReturnValue({
              username: 'abc',
              password: '123',
            });
            await bitbucket.initPlatform({
              endpoint,
              username: 'abc',
              password: '123',
            });
          });
    
          afterEach(async () => {
            await bitbucket.cleanRepo();
          });
    
          describe('initPlatform()', () => {
            it('should throw if no endpoint', () => {
              expect.assertions(1);
              expect(() => bitbucket.initPlatform({})).toThrow();
            });
            it('should throw if no username/password', () => {
              expect.assertions(1);
              expect(() =>
                bitbucket.initPlatform({ endpoint: 'endpoint' })
              ).toThrow();
            });
            it('should init', async () => {
              expect(
                await bitbucket.initPlatform({
                  endpoint: 'https://stash.renovatebot.com',
                  username: 'abc',
                  password: '123',
                })
              ).toMatchSnapshot();
            });
          });
    
          describe('getRepos()', () => {
            it('returns repos', async () => {
              expect.assertions(2);
              httpMock
                .scope(urlHost)
                .get(
                  `${urlPath}/rest/api/1.0/repos?permission=REPO_WRITE&state=AVAILABLE&limit=100`
                )
                .reply(200, {
                  size: 1,
                  limit: 100,
                  isLastPage: true,
                  values: [repoMock(url, 'SOME', 'repo')],
                  start: 0,
                });
              expect(await bitbucket.getRepos()).toEqual(['some/repo']);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('initRepo()', () => {
            it('works', async () => {
              expect.assertions(2);
              httpMock
                .scope(urlHost)
                .get(`${urlPath}/rest/api/1.0/projects/SOME/repos/repo`)
                .reply(200, repoMock(url, 'SOME', 'repo'))
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/branches/default`
                )
                .reply(200, {
                  displayId: 'master',
                });
              expect(
                await bitbucket.initRepo({
                  endpoint: 'https://stash.renovatebot.com/vcs/',
                  repository: 'SOME/repo',
                  localDir: '',
                  optimizeForDisabled: false,
                })
              ).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
            it('does not throw', async () => {
              expect.assertions(2);
              httpMock
                .scope(urlHost)
                .get(`${urlPath}/rest/api/1.0/projects/SOME/repos/repo`)
                .reply(200, repoMock(url, 'SOME', 'repo'))
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/branches/default`
                )
                .reply(200, {
                  displayId: 'master',
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/browse/renovate.json?limit=20000`
                )
                .reply(200, {
                  isLastPage: false,
                  lines: ['{'],
                  size: 50000,
                });
              const res = await bitbucket.initRepo({
                endpoint: 'https://stash.renovatebot.com/vcs/',
                repository: 'SOME/repo',
                localDir: '',
                optimizeForDisabled: true,
              });
              expect(res).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws disabled', async () => {
              expect.assertions(2);
              httpMock
                .scope(urlHost)
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/browse/renovate.json?limit=20000`
                )
                .reply(200, { isLastPage: true, lines: ['{ "enabled": false }'] });
              await expect(
                bitbucket.initRepo({
                  endpoint: 'https://stash.renovatebot.com/vcs/',
                  repository: 'SOME/repo',
                  localDir: '',
                  optimizeForDisabled: true,
                })
              ).rejects.toThrow(REPOSITORY_DISABLED);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('repoForceRebase()', () => {
            it('returns false on missing mergeConfig', async () => {
              expect.assertions(2);
              httpMock
                .scope(urlHost)
                .get(
                  `${urlPath}/rest/api/1.0/projects/undefined/repos/undefined/settings/pull-requests`
                )
                .reply(200, {
                  mergeConfig: null,
                });
              const actual = await bitbucket.getRepoForceRebase();
              expect(actual).toBe(false);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('returns false on missing defaultStrategy', async () => {
              expect.assertions(2);
              httpMock
                .scope(urlHost)
                .get(
                  `${urlPath}/rest/api/1.0/projects/undefined/repos/undefined/settings/pull-requests`
                )
                .reply(200, {
                  mergeConfig: {
                    defaultStrategy: null,
                  },
                });
              const actual = await bitbucket.getRepoForceRebase();
              expect(actual).toBe(false);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it.each(['ff-only', 'rebase-ff-only', 'squash-ff-only'])(
              'return true if %s strategy is enabled',
              async (id) => {
                expect.assertions(2);
                httpMock
                  .scope(urlHost)
                  .get(
                    `${urlPath}/rest/api/1.0/projects/undefined/repos/undefined/settings/pull-requests`
                  )
                  .reply(200, {
                    mergeConfig: {
                      defaultStrategy: {
                        id,
                      },
                    },
                  });
                const actual = await bitbucket.getRepoForceRebase();
                expect(actual).toBe(true);
                expect(httpMock.getTrace()).toMatchSnapshot();
              }
            );
    
            it.each(['no-ff', 'ff', 'rebase-no-ff', 'squash'])(
              'return false if %s strategy is enabled',
              async (id) => {
                expect.assertions(2);
                httpMock
                  .scope(urlHost)
                  .get(
                    `${urlPath}/rest/api/1.0/projects/undefined/repos/undefined/settings/pull-requests`
                  )
                  .reply(200, {
                    mergeConfig: {
                      defaultStrategy: {
                        id,
                      },
                    },
                  });
                const actual = await bitbucket.getRepoForceRebase();
                expect(actual).toBe(false);
                expect(httpMock.getTrace()).toMatchSnapshot();
              }
            );
          });
    
          describe('setBaseBranch()', () => {
            it('updates file list', async () => {
              expect.assertions(1);
              await initRepo();
              await bitbucket.setBaseBranch('branch');
              await bitbucket.setBaseBranch();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getFileList()', () => {
            it('sends to gitFs', async () => {
              await initRepo();
              expect(await bitbucket.getFileList()).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('branchExists()', () => {
            describe('getFileList()', () => {
              it('sends to gitFs', async () => {
                await initRepo();
                expect(
                  await bitbucket.branchExists(undefined as any)
                ).toMatchSnapshot();
                expect(httpMock.getTrace()).toMatchSnapshot();
              });
            });
          });
    
          describe('isBranchStale()', () => {
            it('sends to gitFs', async () => {
              await initRepo();
              expect(
                await bitbucket.isBranchStale(undefined as any)
              ).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('deleteBranch()', () => {
            it('sends to gitFs', async () => {
              await initRepo();
              expect(await bitbucket.deleteBranch('branch')).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('mergeBranch()', () => {
            it('sends to gitFs', async () => {
              await initRepo();
              expect(await bitbucket.mergeBranch('branch')).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('commitFiles()', () => {
            it('sends to gitFs', async () => {
              expect.assertions(1);
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=ALL&role.1=AUTHOR&username.1=abc&limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [prMock(url, 'SOME', 'repo')],
                });
              await bitbucket.commitFiles({
                branchName: 'some-branch',
                files: [{ name: 'test', contents: 'dummy' }],
                message: 'message',
              });
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getFile()', () => {
            it('sends to gitFs', async () => {
              await initRepo();
              expect(await bitbucket.getFile('', '')).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getAllRenovateBranches()', () => {
            it('sends to gitFs', async () => {
              await initRepo();
              expect(await bitbucket.getAllRenovateBranches('')).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getBranchLastCommitTime()', () => {
            it('sends to gitFs', async () => {
              await initRepo();
              expect(await bitbucket.getBranchLastCommitTime('')).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('addAssignees()', () => {
            it('does not throw', async () => {
              expect(await bitbucket.addAssignees(3, ['some'])).toMatchSnapshot();
            });
          });
    
          describe('addReviewers', () => {
            it('does not throw', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge`
                )
                .twice()
                .reply(200, { conflicted: false })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .twice()
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .twice()
                .reply(200, prMock(url, 'SOME', 'repo'))
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(200, prMock(url, 'SOME', 'repo'));
    
              expect(await bitbucket.addReviewers(5, ['name'])).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('sends the reviewer name as a reviewer', async () => {
              expect.assertions(1);
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge`
                )
                .twice()
                .reply(200, { conflicted: false })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .twice()
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .twice()
                .reply(200, prMock(url, 'SOME', 'repo'))
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(200, prMock(url, 'SOME', 'repo'));
    
              await bitbucket.addReviewers(5, ['name']);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws not-found 1', async () => {
              await initRepo();
              await expect(
                bitbucket.addReviewers(null as any, ['name'])
              ).rejects.toThrow(REPOSITORY_NOT_FOUND);
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws not-found 2', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/4`
                )
                .reply(404);
    
              await expect(bitbucket.addReviewers(4, ['name'])).rejects.toThrow(
                REPOSITORY_NOT_FOUND
              );
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws not-found 3', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(404);
    
              await expect(bitbucket.addReviewers(5, ['name'])).rejects.toThrow(
                REPOSITORY_NOT_FOUND
              );
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws repository-changed', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(409);
              await expect(bitbucket.addReviewers(5, ['name'])).rejects.toThrow(
                REPOSITORY_CHANGED
              );
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(405);
              await expect(
                bitbucket.addReviewers(5, ['name'])
              ).rejects.toMatchObject({
                statusCode: 405,
              });
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('deleteLAbel()', () => {
            it('does not throw', async () => {
              expect(await bitbucket.deleteLabel(5, 'renovate')).toMatchSnapshot();
            });
          });
    
          describe('ensureComment()', () => {
            it('does not throw', async () => {
              httpMock
                .scope(urlHost)
                .get(
                  `${urlPath}/rest/api/1.0/projects/undefined/repos/undefined/pull-requests/3/activities?limit=100`
                )
                .reply(200);
              const res = await bitbucket.ensureComment({
                number: 3,
                topic: 'topic',
                content: 'content',
              });
              expect(res).toBe(false);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('add comment if not found 1', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                })
                .post(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments`
                )
                .reply(200);
    
              expect(
                await bitbucket.ensureComment({
                  number: 5,
                  topic: 'topic',
                  content: 'content',
                })
              ).toBe(true);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('add comment if not found 2', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                })
                .post(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments`
                )
                .reply(200);
    
              expect(
                await bitbucket.ensureComment({
                  number: 5,
                  topic: null,
                  content: 'content',
                })
              ).toBe(true);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('add updates comment if necessary 1', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments/21`
                )
                .reply(200, {
                  version: 1,
                })
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments/21`
                )
                .reply(200);
    
              expect(
                await bitbucket.ensureComment({
                  number: 5,
                  topic: 'some-subject',
                  content: 'some\ncontent',
                })
              ).toBe(true);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('add updates comment if necessary 2', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                })
                .post(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments`
                )
                .reply(200);
    
              expect(
                await bitbucket.ensureComment({
                  number: 5,
                  topic: null,
                  content: 'some\ncontent',
                })
              ).toBe(true);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('skips comment 1', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                });
    
              expect(
                await bitbucket.ensureComment({
                  number: 5,
                  topic: 'some-subject',
                  content: 'blablabla',
                })
              ).toBe(true);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('skips comment 2', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                });
    
              const res = await bitbucket.ensureComment({
                number: 5,
                topic: null,
                content: '!merge',
              });
              expect(res).toBe(true);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('ensureCommentRemoval()', () => {
            it('does not throw', async () => {
              httpMock
                .scope(urlHost)
                .get(
                  `${urlPath}/rest/api/1.0/projects/undefined/repos/undefined/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/undefined/repos/undefined/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                });
              await bitbucket.ensureCommentRemoval({ number: 5, topic: 'topic' });
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('deletes comment by topic if found', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments/21`
                )
                .reply(200, {
                  version: 1,
                })
                .delete(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments/21?version=1`
                )
                .reply(200);
    
              await bitbucket.ensureCommentRemoval({
                number: 5,
                topic: 'some-subject',
              });
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('deletes comment by content if found', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments/22`
                )
                .reply(200, {
                  version: 1,
                })
                .delete(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/comments/22?version=1`
                )
                .reply(200);
    
              await bitbucket.ensureCommentRemoval({
                number: 5,
                content: '!merge',
              });
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('deletes nothing', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100`
                )
                .reply(200, {
                  isLastPage: false,
                  nextPageStart: 1,
                  values: [
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 21, text: '### some-subject\n\nblablabla' },
                    },
                    {
                      action: 'COMMENTED',
                      commentAction: 'ADDED',
                      comment: { id: 22, text: '!merge' },
                    },
                  ],
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/activities?limit=100&start=1`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ action: 'OTHER' }],
                });
    
              await bitbucket.ensureCommentRemoval({ number: 5, topic: 'topic' });
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getPrList()', () => {
            it('has pr', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=ALL&role.1=AUTHOR&username.1=abc&limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [prMock(url, 'SOME', 'repo')],
                });
              expect(await bitbucket.getPrList()).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getBranchPr()', () => {
            it('has pr', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=ALL&role.1=AUTHOR&username.1=abc&limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [prMock(url, 'SOME', 'repo')],
                })
                .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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                });
    
              expect(
                await bitbucket.getBranchPr('userName1/pullRequest5')
              ).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
            it('has no pr', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=ALL&role.1=AUTHOR&username.1=abc&limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [prMock(url, 'SOME', 'repo')],
                });
    
              expect(
                await bitbucket.findPr({
                  branchName: 'userName1/pullRequest1',
                })
              ).toBeUndefined();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('findPr()', () => {
            it('has pr', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=ALL&role.1=AUTHOR&username.1=abc&limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [prMock(url, 'SOME', 'repo')],
                });
    
              expect(
                await bitbucket.findPr({
                  branchName: 'userName1/pullRequest5',
                  prTitle: 'title',
                  state: PR_STATE_OPEN,
                })
              ).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
            it('has no pr', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=ALL&role.1=AUTHOR&username.1=abc&limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [prMock(url, 'SOME', 'repo')],
                });
    
              expect(
                await bitbucket.findPr({
                  branchName: 'userName1/pullRequest5',
                  prTitle: 'title',
                  state: PR_STATE_CLOSED,
                })
              ).toBeUndefined();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('createPr()', () => {
            it('posts PR', async () => {
              const scope = await initRepo();
              scope
                .get(`${urlPath}/rest/api/1.0/projects/SOME/repos/repo`)
                .reply(200, prMock(url, 'SOME', 'repo'))
                .get(
                  `${urlPath}/rest/default-reviewers/1.0/projects/SOME/repos/repo/reviewers?sourceRefId=refs/heads/branch&targetRefId=refs/heads/master&sourceRepoId=5&targetRepoId=5`
                )
                .reply(200, [{ name: 'jcitizen' }])
                .post(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests`
                )
                .reply(200, prMock(url, 'SOME', 'repo'));
    
              const { number: id } = await bitbucket.createPr({
                branchName: 'branch',
                prTitle: 'title',
                prBody: 'body',
              });
              expect(id).toBe(5);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('posts PR default branch', async () => {
              const scope = await initRepo();
              scope
                .get(`${urlPath}/rest/api/1.0/projects/SOME/repos/repo`)
                .reply(200, prMock(url, 'SOME', 'repo'))
                .get(
                  `${urlPath}/rest/default-reviewers/1.0/projects/SOME/repos/repo/reviewers?sourceRefId=refs/heads/branch&targetRefId=refs/heads/master&sourceRepoId=5&targetRepoId=5`
                )
                .reply(200, [{ name: 'jcitizen' }])
                .post(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests`
                )
                .reply(200, prMock(url, 'SOME', 'repo'));
    
              const { number: id } = await bitbucket.createPr({
                branchName: 'branch',
                prTitle: 'title',
                prBody: 'body',
                labels: null,
                useDefaultBranch: true,
              });
              expect(id).toBe(5);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getPr()', () => {
            it('returns null for no prNo', async () => {
              httpMock.scope(urlHost);
              expect(await bitbucket.getPr(undefined as any)).toBeNull();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
            it('gets a PR', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                });
    
              expect(await bitbucket.getPr(5)).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('canRebase', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/3`
                )
                .reply(200, prMock(url, 'SOME', 'repo'))
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/3/merge`
                )
                .reply(200, { conflicted: false })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/3/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 2,
                })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .twice()
                .reply(200, prMock(url, 'SOME', 'repo'))
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge`
                )
                .twice()
                .reply(200, { conflicted: false })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .twice()
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                });
    
              const author = global.gitAuthor;
              try {
                expect(await bitbucket.getPr(3)).toMatchSnapshot();
    
                global.gitAuthor = { email: 'bot@renovateapp.com', name: 'bot' };
                expect(await bitbucket.getPr(5)).toMatchSnapshot();
    
                global.gitAuthor = { email: 'jane@example.com', name: 'jane' };
                expect(await bitbucket.getPr(5)).toMatchSnapshot();
    
                expect(httpMock.getTrace()).toMatchSnapshot();
              } finally {
                global.gitAuthor = author;
              }
            });
    
            it('gets a closed PR', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(200, {
                  version: 0,
                  number: 5,
                  state: 'MERGED',
                  reviewers: [],
                  fromRef: {},
                  toRef: {},
                });
    
              expect(await bitbucket.getPr(5)).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('updatePr()', () => {
            it('puts PR', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(200);
    
              await bitbucket.updatePr(5, 'title', 'body');
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws not-found 1', async () => {
              await initRepo();
              await expect(
                bitbucket.updatePr(null as any, 'title', 'body')
              ).rejects.toThrow(REPOSITORY_NOT_FOUND);
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws not-found 2', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/4`
                )
                .reply(404);
              await expect(bitbucket.updatePr(4, 'title', 'body')).rejects.toThrow(
                REPOSITORY_NOT_FOUND
              );
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws not-found 3', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(404);
    
              await expect(bitbucket.updatePr(5, 'title', 'body')).rejects.toThrow(
                REPOSITORY_NOT_FOUND
              );
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws repository-changed', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(409);
    
              await expect(bitbucket.updatePr(5, 'title', 'body')).rejects.toThrow(
                REPOSITORY_CHANGED
              );
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .put(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5`
                )
                .reply(405);
    
              await expect(
                bitbucket.updatePr(5, 'title', 'body')
              ).rejects.toMatchObject({
                statusCode: 405,
              });
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('mergePr()', () => {
            it('posts Merge', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .post(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge?version=1`
                )
                .reply(200);
    
              expect(await bitbucket.mergePr(5, 'branch')).toBe(true);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws not-found 1', async () => {
              await initRepo();
              const res = bitbucket.mergePr(null as any, 'branch');
              await expect(res).rejects.toThrow(REPOSITORY_NOT_FOUND);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws not-found 2', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/4`
                )
                .reply(404);
    
              await expect(bitbucket.mergePr(4, 'branch')).rejects.toThrow(
                REPOSITORY_NOT_FOUND
              );
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws not-found 3', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .post(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge?version=1`
                )
                .reply(404);
    
              await expect(bitbucket.mergePr(5, 'branch')).rejects.toThrow(
                REPOSITORY_NOT_FOUND
              );
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws conflicted', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .post(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge?version=1`
                )
                .reply(409);
    
              expect(await bitbucket.mergePr(5, 'branch')).toBeFalsy();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('unknown error', 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 })
                .get(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/commits?withCounts=true`
                )
                .reply(200, {
                  totalCount: 1,
                  values: [{ author: { emailAddress: 'bot@renovateapp.com' } }],
                })
                .post(
                  `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests/5/merge?version=1`
                )
                .reply(405);
    
              await expect(bitbucket.mergePr(5, 'branch')).resolves.toBe(false);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getPrBody()', () => {
            it('returns diff files', () => {
              expect(
                bitbucket.getPrBody(
                  '<details><summary>foo</summary>bar</details>text<details>'
                )
              ).toMatchSnapshot();
            });
    
            it('sanitizes HTML comments in the body', () => {
              const prBody = bitbucket.getPrBody(`---
    
    - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
    - [ ] <!-- recreate-branch=renovate/docker-renovate-renovate-16.x --><a href="/some/link">Update renovate/renovate to 16.1.2</a>
    
    ---
    <!---->
    Empty comment.
    <!-- This is another comment -->
    Followed by some information.
    <!-- followed by some more comments -->`);
              expect(prBody).toMatchSnapshot();
            });
          });
    
          describe('getCommitMessages()', () => {
            it('sends to gitFs', async () => {
              await initRepo();
              expect(await bitbucket.getCommitMessages()).toMatchSnapshot();
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getVulnerabilityAlerts()', () => {
            it('returns empty array', async () => {
              expect.assertions(1);
              expect(await bitbucket.getVulnerabilityAlerts()).toEqual([]);
            });
          });
    
          describe('getBranchStatus()', () => {
            it('should be success', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/stats/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200, {
                  successful: 3,
                  inProgress: 0,
                  failed: 0,
                });
    
              expect(await bitbucket.getBranchStatus('somebranch', [])).toEqual(
                BranchStatus.green
              );
    
              expect(await bitbucket.getBranchStatus('somebranch')).toEqual(
                BranchStatus.green
              );
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('should be pending', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/stats/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200, {
                  successful: 3,
                  inProgress: 1,
                  failed: 0,
                });
    
              expect(await bitbucket.getBranchStatus('somebranch', [])).toEqual(
                BranchStatus.yellow
              );
    
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/stats/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200, {
                  successful: 0,
                  inProgress: 0,
                  failed: 0,
                });
    
              expect(await bitbucket.getBranchStatus('somebranch', [])).toEqual(
                BranchStatus.yellow
              );
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('should be failed', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/stats/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200, {
                  successful: 1,
                  inProgress: 1,
                  failed: 1,
                });
    
              expect(await bitbucket.getBranchStatus('somebranch', [])).toEqual(
                BranchStatus.red
              );
    
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/stats/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .replyWithError('requst-failed');
    
              expect(await bitbucket.getBranchStatus('somebranch', [])).toEqual(
                BranchStatus.red
              );
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('throws repository-changed', async () => {
              git.branchExists.mockResolvedValue(false);
              await initRepo();
              await expect(
                bitbucket.getBranchStatus('somebranch', [])
              ).rejects.toThrow(REPOSITORY_CHANGED);
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('getBranchStatusCheck()', () => {
            it('should be success', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [
                    {
                      state: 'SUCCESSFUL',
                      key: 'context-2',
                      url: 'https://renovatebot.com',
                    },
                  ],
                });
    
              expect(
                await bitbucket.getBranchStatusCheck('somebranch', 'context-2')
              ).toEqual(BranchStatus.green);
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('should be pending', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [
                    {
                      state: 'INPROGRESS',
                      key: 'context-2',
                      url: 'https://renovatebot.com',
                    },
                  ],
                });
    
              expect(
                await bitbucket.getBranchStatusCheck('somebranch', 'context-2')
              ).toEqual(BranchStatus.yellow);
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('should be failure', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [
                    {
                      state: 'FAILED',
                      key: 'context-2',
                      url: 'https://renovatebot.com',
                    },
                  ],
                });
    
              expect(
                await bitbucket.getBranchStatusCheck('somebranch', 'context-2')
              ).toEqual(BranchStatus.red);
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
    
            it('should be null', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .replyWithError('requst-failed');
    
              expect(
                await bitbucket.getBranchStatusCheck('somebranch', 'context-2')
              ).toBeNull();
    
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [],
                });
    
              expect(
                await bitbucket.getBranchStatusCheck('somebranch', 'context-2')
              ).toBeNull();
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
    
          describe('setBranchStatus()', () => {
            it('should be success 1', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .twice()
                .reply(200, {
                  isLastPage: true,
                  values: [{ key: 'context-1', state: 'SUCCESSFUL' }],
                })
                .post(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200)
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/stats/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200, {});
    
              await bitbucket.setBranchStatus({
                branchName: 'somebranch',
                context: 'context-2',
                description: null as any,
                state: BranchStatus.green,
              });
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
            it('should be success 2', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .twice()
                .reply(200, {
                  isLastPage: true,
                  values: [{ key: 'context-1', state: 'SUCCESSFUL' }],
                })
                .post(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200)
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/stats/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200, {});
    
              await bitbucket.setBranchStatus({
                branchName: 'somebranch',
                context: 'context-2',
                description: null as any,
                state: BranchStatus.red,
              });
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
            it('should be success 3', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .twice()
                .reply(200, {
                  isLastPage: true,
                  values: [{ key: 'context-1', state: 'SUCCESSFUL' }],
                })
                .post(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200)
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/stats/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200, {});
    
              await bitbucket.setBranchStatus({
                branchName: 'somebranch',
                context: 'context-2',
                description: null as any,
                state: BranchStatus.red,
              });
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
            it('should be success 4', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .twice()
                .reply(200, {
                  isLastPage: true,
                  values: [{ key: 'context-1', state: 'SUCCESSFUL' }],
                })
                .post(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200)
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/stats/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .reply(200, {});
    
              await bitbucket.setBranchStatus({
                branchName: 'somebranch',
                context: 'context-2',
                description: null as any,
                state: BranchStatus.yellow,
              });
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
            it('should be success 5', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ key: 'context-1', state: 'SUCCESSFUL' }],
                })
                .post(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e`
                )
                .replyWithError('requst-failed');
    
              await bitbucket.setBranchStatus({
                branchName: 'somebranch',
                context: 'context-2',
                description: null as any,
                state: BranchStatus.green,
              });
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
            it('should be success 6', async () => {
              const scope = await initRepo();
              scope
                .get(
                  `${urlPath}/rest/build-status/1.0/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e?limit=100`
                )
                .reply(200, {
                  isLastPage: true,
                  values: [{ key: 'context-1', state: 'SUCCESSFUL' }],
                });
    
              await bitbucket.setBranchStatus({
                branchName: 'somebranch',
                context: 'context-1',
                description: null as any,
                state: BranchStatus.green,
              });
    
              expect(httpMock.getTrace()).toMatchSnapshot();
            });
          });
        });
      });
    });