From b4b66183f06c5f4e7de043918f3dcf44bcb24443 Mon Sep 17 00:00:00 2001
From: Sergio Zharinov <zharinov@users.noreply.github.com>
Date: Mon, 1 Jun 2020 17:35:12 +0400
Subject: [PATCH] refactor(bitbucket-server): Switch to new http wrapper
 (#6393)

---
 .../bitbucket-server/bb-got-wrapper.ts        | 40 ----------
 lib/platform/bitbucket-server/index.ts        | 80 ++++++++++++-------
 lib/platform/bitbucket-server/utils.ts        | 35 +++++++-
 lib/util/http/bitbucket-server.ts             | 30 +++++++
 4 files changed, 112 insertions(+), 73 deletions(-)
 delete mode 100644 lib/platform/bitbucket-server/bb-got-wrapper.ts
 create mode 100644 lib/util/http/bitbucket-server.ts

diff --git a/lib/platform/bitbucket-server/bb-got-wrapper.ts b/lib/platform/bitbucket-server/bb-got-wrapper.ts
deleted file mode 100644
index 6ffbb03b75..0000000000
--- a/lib/platform/bitbucket-server/bb-got-wrapper.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import URL from 'url';
-import { GotJSONOptions } from 'got';
-import { PLATFORM_TYPE_BITBUCKET_SERVER } from '../../constants/platforms';
-import got from '../../util/got';
-import { GotApi, GotApiOptions, GotResponse } from '../common';
-
-let baseUrl: string;
-
-function get(
-  path: string,
-  options: GotApiOptions & GotJSONOptions
-): Promise<GotResponse> {
-  const url = URL.resolve(baseUrl, path);
-  const opts: GotApiOptions & GotJSONOptions = {
-    hostType: PLATFORM_TYPE_BITBUCKET_SERVER,
-    json: true,
-    ...options,
-  };
-  opts.headers = {
-    ...opts.headers,
-    'X-Atlassian-Token': 'no-check',
-  };
-  return got(url, opts);
-}
-
-const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete'];
-
-export const api: GotApi = {} as any;
-
-for (const x of helpers) {
-  (api as any)[x] = (url: string, opts: any): Promise<GotResponse> =>
-    get(url, { ...opts, method: x.toUpperCase() });
-}
-
-// eslint-disable-next-line @typescript-eslint/unbound-method
-api.setBaseUrl = (e: string): void => {
-  baseUrl = e;
-};
-
-export default api;
diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts
index 755e4b668e..a65f648a03 100644
--- a/lib/platform/bitbucket-server/index.ts
+++ b/lib/platform/bitbucket-server/index.ts
@@ -11,6 +11,11 @@ import { PR_STATE_ALL, PR_STATE_OPEN } from '../../constants/pull-requests';
 import { logger } from '../../logger';
 import { BranchStatus } from '../../types';
 import * as hostRules from '../../util/host-rules';
+import { HttpResponse } from '../../util/http';
+import {
+  BitbucketServerHttp,
+  setBaseUrl,
+} from '../../util/http/bitbucket-server';
 import { sanitize } from '../../util/sanitize';
 import { ensureTrailingSlash } from '../../util/url';
 import {
@@ -22,7 +27,6 @@ import {
   EnsureIssueConfig,
   EnsureIssueResult,
   FindPRConfig,
-  GotResponse,
   Issue,
   PlatformConfig,
   Pr,
@@ -32,7 +36,6 @@ import {
 } from '../common';
 import GitStorage, { StatusResult } from '../git/storage';
 import { smartTruncate } from '../utils/pr-body';
-import { api } from './bb-got-wrapper';
 import { BbbsRestPr, BbsConfig, BbsPr, BbsRestUserRef } from './types';
 import * as utils from './utils';
 import { PartialDeep } from 'type-fest';
@@ -48,6 +51,8 @@ import { PartialDeep } from 'type-fest';
 
 let config: BbsConfig = {} as any;
 
+const bitbucketServerHttp = new BitbucketServerHttp();
+
 const defaults: any = {
   hostType: PLATFORM_TYPE_BITBUCKET_SERVER,
 };
@@ -74,7 +79,7 @@ export function initPlatform({
   }
   // TODO: Add a connection check that endpoint/username/password combination are valid
   defaults.endpoint = ensureTrailingSlash(endpoint);
-  api.setBaseUrl(defaults.endpoint);
+  setBaseUrl(defaults.endpoint);
   const platformConfig: PlatformConfig = {
     endpoint: defaults.endpoint,
   };
@@ -141,7 +146,7 @@ export async function initRepo({
 
     let renovateConfig: RenovateConfig;
     try {
-      const { body } = await api.get<FileData>(
+      const { body } = await bitbucketServerHttp.getJson<FileData>(
         `./rest/api/1.0/projects/${projectKey}/repos/${repositorySlug}/browse/renovate.json?limit=20000`
       );
       if (!body.isLastPage) {
@@ -190,14 +195,17 @@ export async function initRepo({
 
   try {
     const info = (
-      await api.get(
+      await bitbucketServerHttp.getJson<{
+        project: { key: string };
+        parent: string;
+      }>(
         `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}`
       )
     ).body;
     config.owner = info.project.key;
     logger.debug(`${repository} owner = ${config.owner}`);
     config.defaultBranch = (
-      await api.get(
+      await bitbucketServerHttp.getJson<{ displayId: string }>(
         `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/branches/default`
       )
     ).body.displayId;
@@ -222,7 +230,9 @@ export async function getRepoForceRebase(): Promise<boolean> {
   logger.debug(`getRepoForceRebase()`);
 
   // https://docs.atlassian.com/bitbucket-server/rest/7.0.1/bitbucket-rest.html#idp342
-  const res = await api.get(
+  const res = await bitbucketServerHttp.getJson<{
+    mergeConfig: { defaultStrategy: { id: string } };
+  }>(
     `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/settings/pull-requests`
   );
 
@@ -233,7 +243,7 @@ export async function getRepoForceRebase(): Promise<boolean> {
   return Boolean(
     res.body.mergeConfig &&
       res.body.mergeConfig.defaultStrategy &&
-      res.body.mergeConfig.defaultStrategy.id.indexOf('ff-only') >= 0
+      res.body.mergeConfig.defaultStrategy.id.includes('ff-only')
   );
 }
 
@@ -281,7 +291,7 @@ export async function getPr(
     return null;
   }
 
-  const res = await api.get(
+  const res = await bitbucketServerHttp.getJson<BbbsRestPr>(
     `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}`,
     { useCache: !refreshCache }
   );
@@ -289,16 +299,17 @@ export async function getPr(
   const pr: BbsPr = {
     displayNumber: `Pull Request #${res.body.id}`,
     ...utils.prInfo(res.body),
-    reviewers: res.body.reviewers.map(
-      (r: { user: { name: string } }) => r.user.name
-    ),
+    reviewers: res.body.reviewers.map((r) => r.user.name),
     isModified: false,
   };
 
   pr.version = updatePrVersion(pr.number, pr.version);
 
   if (pr.state === PR_STATE_OPEN) {
-    const mergeRes = await api.get(
+    const mergeRes = await bitbucketServerHttp.getJson<{
+      conflicted: string;
+      canMerge: string;
+    }>(
       `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/merge`,
       { useCache: !refreshCache }
     );
@@ -306,7 +317,10 @@ export async function getPr(
     pr.canMerge = !!mergeRes.body.canMerge;
 
     const prCommits = (
-      await api.get(
+      await bitbucketServerHttp.getJson<{
+        totalCount: number;
+        values: { author: { emailAddress: string } }[];
+      }>(
         `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/commits?withCounts=true`,
         { useCache: !refreshCache }
       )
@@ -474,7 +488,7 @@ export async function deleteBranch(
     // getBranchPr
     const pr = await getBranchPr(branchName);
     if (pr) {
-      const { body } = await api.post<{ version: number }>(
+      const { body } = await bitbucketServerHttp.postJson<{ version: number }>(
         `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${pr.number}/decline?version=${pr.version}`
       );
 
@@ -507,9 +521,12 @@ async function getStatus(
   const branchCommit = await config.storage.getBranchCommit(branchName);
 
   return (
-    await api.get(`./rest/build-status/1.0/commits/stats/${branchCommit}`, {
-      useCache,
-    })
+    await bitbucketServerHttp.getJson<utils.BitbucketCommitStatus>(
+      `./rest/build-status/1.0/commits/stats/${branchCommit}`,
+      {
+        useCache,
+      }
+    )
   ).body;
 }
 
@@ -633,7 +650,10 @@ export async function setBranchStatus({
         break;
     }
 
-    await api.post(`./rest/build-status/1.0/commits/${branchCommit}`, { body });
+    await bitbucketServerHttp.postJson(
+      `./rest/build-status/1.0/commits/${branchCommit}`,
+      { body }
+    );
 
     // update status cache
     await getStatus(branchName, false);
@@ -711,7 +731,7 @@ export async function addReviewers(
 
     const reviewersSet = new Set([...pr.reviewers, ...reviewers]);
 
-    await api.put(
+    await bitbucketServerHttp.putJson(
       `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}`,
       {
         body: {
@@ -765,7 +785,7 @@ async function getComments(prNo: number): Promise<Comment[]> {
 
 async function addComment(prNo: number, text: string): Promise<void> {
   // POST /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments
-  await api.post(
+  await bitbucketServerHttp.postJson(
     `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/comments`,
     {
       body: { text },
@@ -779,7 +799,7 @@ async function getCommentVersion(
 ): Promise<number> {
   // GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}
   const { version } = (
-    await api.get(
+    await bitbucketServerHttp.getJson<{ version: number }>(
       `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/comments/${commentId}`
     )
   ).body;
@@ -795,7 +815,7 @@ async function editComment(
   const version = await getCommentVersion(prNo, commentId);
 
   // PUT /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}
-  await api.put(
+  await bitbucketServerHttp.putJson(
     `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/comments/${commentId}`,
     {
       body: { text, version },
@@ -807,7 +827,7 @@ async function deleteComment(prNo: number, commentId: number): Promise<void> {
   const version = await getCommentVersion(prNo, commentId);
 
   // DELETE /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}
-  await api.delete(
+  await bitbucketServerHttp.deleteJson(
     `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/comments/${commentId}?version=${version}`
   );
 }
@@ -916,13 +936,13 @@ export async function createPr({
   if (config.bbUseDefaultReviewers) {
     logger.debug(`fetching default reviewers`);
     const { id } = (
-      await api.get<{ id: number }>(
+      await bitbucketServerHttp.getJson<{ id: number }>(
         `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}`
       )
     ).body;
 
     const defReviewers = (
-      await api.get<{ name: string }[]>(
+      await bitbucketServerHttp.getJson<{ name: string }[]>(
         `./rest/default-reviewers/1.0/projects/${config.projectKey}/repos/${
           config.repositorySlug
         }/reviewers?sourceRefId=refs/heads/${escapeHash(
@@ -947,9 +967,9 @@ export async function createPr({
     },
     reviewers,
   };
-  let prInfoRes: GotResponse<BbbsRestPr>;
+  let prInfoRes: HttpResponse<BbbsRestPr>;
   try {
-    prInfoRes = await api.post<BbbsRestPr>(
+    prInfoRes = await bitbucketServerHttp.postJson<BbbsRestPr>(
       `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests`,
       { body }
     );
@@ -1000,7 +1020,7 @@ export async function updatePr(
       throw Object.assign(new Error(REPOSITORY_NOT_FOUND), { statusCode: 404 });
     }
 
-    const { body } = await api.put<{ version: number }>(
+    const { body } = await bitbucketServerHttp.putJson<{ version: number }>(
       `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}`,
       {
         body: {
@@ -1037,7 +1057,7 @@ export async function mergePr(
     if (!pr) {
       throw Object.assign(new Error(REPOSITORY_NOT_FOUND), { statusCode: 404 });
     }
-    const { body } = await api.post<{ version: number }>(
+    const { body } = await bitbucketServerHttp.postJson<{ version: number }>(
       `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests/${prNo}/merge?version=${pr.version}`
     );
     updatePrVersion(prNo, body.version);
diff --git a/lib/platform/bitbucket-server/utils.ts b/lib/platform/bitbucket-server/utils.ts
index d9982c79c2..ea90482844 100644
--- a/lib/platform/bitbucket-server/utils.ts
+++ b/lib/platform/bitbucket-server/utils.ts
@@ -5,9 +5,12 @@ import {
   PR_STATE_MERGED,
   PR_STATE_OPEN,
 } from '../../constants/pull-requests';
-import { api } from './bb-got-wrapper';
+import { HttpResponse } from '../../util/http';
+import { BitbucketServerHttp } from '../../util/http/bitbucket-server';
 import { BbbsRestPr, BbsPr } from './types';
 
+const bitbucketServerHttp = new BitbucketServerHttp();
+
 // https://docs.atlassian.com/bitbucket-server/rest/6.0.0/bitbucket-rest.html#idp250
 const prStateMapping: any = {
   MERGED: PR_STATE_MERGED,
@@ -38,6 +41,29 @@ const addMaxLength = (inputUrl: string, limit = 100): string => {
   return maxedUrl;
 };
 
+async function callApi<T>(
+  apiUrl: string,
+  method: string,
+  options?: any
+): Promise<HttpResponse<T>> {
+  /* istanbul ignore next */
+  switch (method.toLowerCase()) {
+    case 'post':
+      return bitbucketServerHttp.postJson<T>(apiUrl, options);
+    case 'put':
+      return bitbucketServerHttp.putJson<T>(apiUrl, options);
+    case 'patch':
+      return bitbucketServerHttp.patchJson<T>(apiUrl, options);
+    case 'head':
+      return bitbucketServerHttp.headJson<T>(apiUrl, options);
+    case 'delete':
+      return bitbucketServerHttp.deleteJson<T>(apiUrl, options);
+    case 'get':
+    default:
+      return bitbucketServerHttp.getJson<T>(apiUrl, options);
+  }
+}
+
 export async function accumulateValues<T = any>(
   reqUrl: string,
   method = 'get',
@@ -46,11 +72,14 @@ export async function accumulateValues<T = any>(
 ): Promise<T[]> {
   let accumulator: T[] = [];
   let nextUrl = addMaxLength(reqUrl, limit);
-  const lowerCaseMethod = method.toLocaleLowerCase();
 
   while (typeof nextUrl !== 'undefined') {
     // TODO: fix typing
-    const { body } = await (api as any)[lowerCaseMethod](nextUrl, options);
+    const { body } = await callApi<{
+      values: T[];
+      isLastPage: boolean;
+      nextPageStart: string;
+    }>(nextUrl, method, options);
     accumulator = [...accumulator, ...body.values];
     if (body.isLastPage !== false) {
       break;
diff --git a/lib/util/http/bitbucket-server.ts b/lib/util/http/bitbucket-server.ts
new file mode 100644
index 0000000000..6c7661feff
--- /dev/null
+++ b/lib/util/http/bitbucket-server.ts
@@ -0,0 +1,30 @@
+import URL from 'url';
+import { PLATFORM_TYPE_BITBUCKET_SERVER } from '../../constants/platforms';
+import { Http, HttpOptions, HttpResponse, InternalHttpOptions } from '.';
+
+let baseUrl: string;
+export const setBaseUrl = (url: string): void => {
+  baseUrl = url;
+};
+
+export class BitbucketServerHttp extends Http {
+  constructor(options?: HttpOptions) {
+    super(PLATFORM_TYPE_BITBUCKET_SERVER, options);
+  }
+
+  protected async request<T>(
+    path: string,
+    options?: InternalHttpOptions
+  ): Promise<HttpResponse<T> | null> {
+    const url = URL.resolve(baseUrl, path);
+    const opts = {
+      baseUrl,
+      ...options,
+    };
+    opts.headers = {
+      ...opts.headers,
+      'X-Atlassian-Token': 'no-check',
+    };
+    return super.request<T>(url, opts);
+  }
+}
-- 
GitLab