From fcced24a6ad6dda6edfb216e09c07edc2c4fcffe Mon Sep 17 00:00:00 2001 From: Sergio Zharinov <zharinov@users.noreply.github.com> Date: Sat, 16 May 2020 12:53:11 +0400 Subject: [PATCH] refactor(github): Remove old Github platform wrappers (#6203) * refactor(github): Remove old Github platform wrappers * Refactor 'util/cache/run' imports * Fix pod http client * Fix test * refactor(pod): Split request functions Co-authored-by: Michael Kriese <michael.kriese@visualon.de> Co-authored-by: Rhys Arkins <rhys@arkins.net> --- .../__snapshots__/index.spec.ts.snap | 16 + lib/datasource/github-releases/index.spec.ts | 44 +- lib/datasource/github-releases/index.ts | 8 +- .../__snapshots__/index.spec.ts.snap | 123 ++ lib/datasource/github-tags/index.spec.ts | 98 +- lib/datasource/github-tags/index.ts | 23 +- .../pod/__snapshots__/index.spec.ts.snap | 107 ++ lib/datasource/pod/index.spec.ts | 131 +- lib/datasource/pod/index.ts | 69 +- .../github/__snapshots__/index.spec.ts.snap | 1280 ++++++----------- lib/platform/github/gh-got-wrapper.spec.ts | 239 --- lib/platform/github/gh-got-wrapper.ts | 214 --- .../github/gh-graphql-wrapper.spec.ts | 158 -- lib/platform/github/gh-graphql-wrapper.ts | 100 -- lib/platform/github/index.spec.ts | 51 +- lib/platform/github/index.ts | 289 ++-- .../__snapshots__/index.spec.ts.snap | 256 ++++ lib/workers/pr/changelog/index.spec.ts | 113 +- lib/workers/pr/changelog/release-notes.ts | 12 +- lib/workers/pr/changelog/source-github.ts | 6 +- 20 files changed, 1392 insertions(+), 1945 deletions(-) create mode 100644 lib/datasource/pod/__snapshots__/index.spec.ts.snap delete mode 100644 lib/platform/github/gh-got-wrapper.spec.ts delete mode 100644 lib/platform/github/gh-got-wrapper.ts delete mode 100644 lib/platform/github/gh-graphql-wrapper.spec.ts delete mode 100644 lib/platform/github/gh-graphql-wrapper.ts diff --git a/lib/datasource/github-releases/__snapshots__/index.spec.ts.snap b/lib/datasource/github-releases/__snapshots__/index.spec.ts.snap index 3ec77f6047..28c3887d35 100644 --- a/lib/datasource/github-releases/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/github-releases/__snapshots__/index.spec.ts.snap @@ -27,3 +27,19 @@ Object { "sourceUrl": "https://github.com/some/dep", } `; + +exports[`datasource/github-releases getReleases returns releases 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/dep/releases?per_page=100", + }, +] +`; diff --git a/lib/datasource/github-releases/index.spec.ts b/lib/datasource/github-releases/index.spec.ts index 7044733aaa..5e4da876cc 100644 --- a/lib/datasource/github-releases/index.spec.ts +++ b/lib/datasource/github-releases/index.spec.ts @@ -1,26 +1,41 @@ -import { api } from '../../platform/github/gh-got-wrapper'; +import * as httpMock from '../../../test/httpMock'; import * as globalCache from '../../util/cache/global'; - +import * as runCache from '../../util/cache/run'; +import * as _hostRules from '../../util/host-rules'; import * as github from '.'; -jest.mock('../../platform/github/gh-got-wrapper'); -jest.mock('../../util/got'); jest.mock('../../util/host-rules'); +const hostRules: any = _hostRules; -const ghGot: any = api.get; +const githubApiHost = 'https://api.github.com'; describe('datasource/github-releases', () => { - beforeEach(() => globalCache.rmAll()); + beforeEach(async () => { + await globalCache.rmAll(); + hostRules.hosts = jest.fn(() => []); + hostRules.find.mockReturnValue({ + token: 'some-token', + }); + httpMock.setup(); + }); + + afterEach(() => { + httpMock.reset(); + runCache.clear(); + }); + describe('getReleases', () => { - beforeAll(() => globalCache.rmAll()); it('returns releases', async () => { - const body = [ - { tag_name: 'a', published_at: '2020-03-09T13:00:00Z' }, - { tag_name: 'v', published_at: '2020-03-09T12:00:00Z' }, - { tag_name: '1.0.0', published_at: '2020-03-09T11:00:00Z' }, - { tag_name: 'v1.1.0', published_at: '2020-03-09T10:00:00Z' }, - ]; - ghGot.mockReturnValueOnce({ headers: {}, body }); + httpMock + .scope(githubApiHost) + .get('/repos/some/dep/releases?per_page=100') + .reply(200, [ + { tag_name: 'a', published_at: '2020-03-09T13:00:00Z' }, + { tag_name: 'v', published_at: '2020-03-09T12:00:00Z' }, + { tag_name: '1.0.0', published_at: '2020-03-09T11:00:00Z' }, + { tag_name: 'v1.1.0', published_at: '2020-03-09T10:00:00Z' }, + ]); + const res = await github.getReleases({ lookupName: 'some/dep', }); @@ -29,6 +44,7 @@ describe('datasource/github-releases', () => { expect( res.releases.find((release) => release.version === 'v1.1.0') ).toBeDefined(); + expect(httpMock.getTrace()).toMatchSnapshot(); }); }); }); diff --git a/lib/datasource/github-releases/index.ts b/lib/datasource/github-releases/index.ts index aca91e305e..8b4c2c89dc 100644 --- a/lib/datasource/github-releases/index.ts +++ b/lib/datasource/github-releases/index.ts @@ -1,14 +1,14 @@ import { logger } from '../../logger'; -import { api } from '../../platform/github/gh-got-wrapper'; import * as globalCache from '../../util/cache/global'; +import { GithubHttp } from '../../util/http/github'; import { GetReleasesConfig, ReleaseResult } from '../common'; -const { get: ghGot } = api; - export const id = 'github-releases'; const cacheNamespace = 'datasource-github-releases'; +const http = new GithubHttp(); + type GithubRelease = { tag_name: string; published_at: string; @@ -38,7 +38,7 @@ export async function getReleases({ } try { const url = `https://api.github.com/repos/${repo}/releases?per_page=100`; - const res = await ghGot<GithubRelease[]>(url, { + const res = await http.getJson<GithubRelease[]>(url, { paginate: true, }); githubReleases = res.body; diff --git a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap index 527501df58..14cf30b51d 100644 --- a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap @@ -1,5 +1,112 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`datasource/github-tags getDigest returns commit digest 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/dep/git/refs/tags/v1.2.0", + }, +] +`; + +exports[`datasource/github-tags getDigest returns digest 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/dep/commits?per_page=1", + }, +] +`; + +exports[`datasource/github-tags getDigest returns null for missed tagged digest 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/dep/git/refs/tags/v1.2.0", + }, +] +`; + +exports[`datasource/github-tags getDigest returns null if no token 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/dep/commits?per_page=1", + }, +] +`; + +exports[`datasource/github-tags getDigest returns tagged commit digest 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/dep/git/refs/tags/v1.2.0", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/some-url", + }, +] +`; + +exports[`datasource/github-tags getDigest warns if unknown ref 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/dep/git/refs/tags/v1.2.0", + }, +] +`; + exports[`datasource/github-tags getReleases returns tags 1`] = ` Object { "releases": Array [ @@ -15,3 +122,19 @@ Object { "sourceUrl": "https://github.com/some/dep2", } `; + +exports[`datasource/github-tags getReleases returns tags 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/dep2/tags?per_page=100", + }, +] +`; diff --git a/lib/datasource/github-tags/index.spec.ts b/lib/datasource/github-tags/index.spec.ts index 8fb288b979..c47b0526f2 100644 --- a/lib/datasource/github-tags/index.spec.ts +++ b/lib/datasource/github-tags/index.spec.ts @@ -1,75 +1,111 @@ -import { api } from '../../platform/github/gh-got-wrapper'; +import * as httpMock from '../../../test/httpMock'; import * as globalCache from '../../util/cache/global'; import * as runCache from '../../util/cache/run'; import * as _hostRules from '../../util/host-rules'; import * as github from '.'; -jest.mock('../../platform/github/gh-got-wrapper'); -jest.mock('../../util/got'); jest.mock('../../util/host-rules'); - -const ghGot: any = api.get; const hostRules: any = _hostRules; +const githubApiHost = 'https://api.github.com'; + describe('datasource/github-tags', () => { - beforeEach(() => globalCache.rmAll()); + beforeEach(async () => { + httpMock.setup(); + await globalCache.rmAll(); + }); + + afterEach(() => { + runCache.clear(); + httpMock.reset(); + }); + describe('getDigest', () => { + const lookupName = 'some/dep'; + const tag = 'v1.2.0'; + beforeEach(() => { jest.resetAllMocks(); hostRules.hosts = jest.fn(() => []); - runCache.clear(); + hostRules.find.mockReturnValue({ + token: 'some-token', + }); return globalCache.rmAll(); }); + it('returns null if no token', async () => { - ghGot.mockReturnValueOnce({ body: [] }); - const res = await github.getDigest({ lookupName: 'some/dep' }, null); + httpMock + .scope(githubApiHost) + .get(`/repos/${lookupName}/commits?per_page=1`) + .reply(200, []); + const res = await github.getDigest({ lookupName }, null); expect(res).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns digest', async () => { - ghGot.mockReturnValueOnce({ body: [{ sha: 'abcdef' }] }); - const res = await github.getDigest({ lookupName: 'some/dep' }, null); + httpMock + .scope(githubApiHost) + .get(`/repos/${lookupName}/commits?per_page=1`) + .reply(200, [{ sha: 'abcdef' }]); + const res = await github.getDigest({ lookupName }, null); expect(res).toBe('abcdef'); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns commit digest', async () => { - ghGot.mockReturnValueOnce({ - body: { object: { type: 'commit', sha: 'ddd111' } }, - }); - const res = await github.getDigest({ lookupName: 'some/dep' }, 'v1.2.0'); + httpMock + .scope(githubApiHost) + .get(`/repos/${lookupName}/git/refs/tags/${tag}`) + .reply(200, { object: { type: 'commit', sha: 'ddd111' } }); + const res = await github.getDigest({ lookupName }, tag); expect(res).toBe('ddd111'); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns tagged commit digest', async () => { - ghGot.mockReturnValueOnce({ - body: { object: { type: 'tag', url: 'some-url' } }, - }); - ghGot.mockReturnValueOnce({ - body: { object: { type: 'commit', sha: 'ddd111' } }, - }); - const res = await github.getDigest({ lookupName: 'some/dep' }, 'v1.2.0'); + httpMock + .scope(githubApiHost) + .get(`/repos/${lookupName}/git/refs/tags/${tag}`) + .reply(200, { + object: { type: 'tag', url: `${githubApiHost}/some-url` }, + }) + .get('/some-url') + .reply(200, { object: { type: 'commit', sha: 'ddd111' } }); + const res = await github.getDigest({ lookupName }, tag); expect(res).toBe('ddd111'); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('warns if unknown ref', async () => { - ghGot.mockReturnValueOnce({ - body: { object: { sha: 'ddd111' } }, - }); - const res = await github.getDigest({ lookupName: 'some/dep' }, 'v1.2.0'); + httpMock + .scope(githubApiHost) + .get(`/repos/${lookupName}/git/refs/tags/${tag}`) + .reply(200, { object: { sha: 'ddd111' } }); + const res = await github.getDigest({ lookupName }, tag); expect(res).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns null for missed tagged digest', async () => { - ghGot.mockReturnValueOnce({}); + httpMock + .scope(githubApiHost) + .get(`/repos/${lookupName}/git/refs/tags/${tag}`) + .reply(200, {}); const res = await github.getDigest({ lookupName: 'some/dep' }, 'v1.2.0'); expect(res).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); }); }); describe('getReleases', () => { + const lookupName = 'some/dep2'; + beforeAll(() => globalCache.rmAll()); it('returns tags', async () => { const body = [{ name: 'v1.0.0' }, { name: 'v1.1.0' }]; - ghGot.mockReturnValueOnce({ headers: {}, body }); - const res = await github.getReleases({ - lookupName: 'some/dep2', - }); + httpMock + .scope(githubApiHost) + .get(`/repos/${lookupName}/tags?per_page=100`) + .reply(200, body); + const res = await github.getReleases({ lookupName }); expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(2); + expect(httpMock.getTrace()).toMatchSnapshot(); }); }); }); diff --git a/lib/datasource/github-tags/index.ts b/lib/datasource/github-tags/index.ts index 56054f1aca..fea2bae705 100644 --- a/lib/datasource/github-tags/index.ts +++ b/lib/datasource/github-tags/index.ts @@ -1,17 +1,25 @@ import { logger } from '../../logger'; -import { api } from '../../platform/github/gh-got-wrapper'; import * as globalCache from '../../util/cache/global'; +import { GithubHttp } from '../../util/http/github'; import { DigestConfig, GetReleasesConfig, ReleaseResult } from '../common'; -const { get: ghGot } = api; - export const id = 'github-tags'; +const http = new GithubHttp(); + const cacheNamespace = 'datasource-github-tags'; function getCacheKey(repo: string, type: string): string { return `${repo}:${type}`; } +interface TagResponse { + object: { + type: string; + url: string; + sha: string; + }; +} + async function getTagCommit( githubRepo: string, tag: string @@ -27,11 +35,11 @@ async function getTagCommit( let digest: string; try { const url = `https://api.github.com/repos/${githubRepo}/git/refs/tags/${tag}`; - const res = (await ghGot(url)).body.object; + const res = (await http.getJson<TagResponse>(url)).body.object; if (res.type === 'commit') { digest = res.sha; } else if (res.type === 'tag') { - digest = (await ghGot(res.url)).body.object.sha; + digest = (await http.getJson<TagResponse>(res.url)).body.object.sha; } else { logger.warn({ res }, 'Unknown git tag refs type'); } @@ -79,7 +87,8 @@ export async function getDigest( let digest: string; try { const url = `https://api.github.com/repos/${githubRepo}/commits?per_page=1`; - digest = (await ghGot(url)).body[0].sha; + const res = await http.getJson<{ sha: string }[]>(url); + digest = res.body[0].sha; } catch (err) { logger.debug( { githubRepo, err }, @@ -129,7 +138,7 @@ export async function getReleases({ }[]; versions = ( - await ghGot<GitHubTag>(url, { + await http.getJson<GitHubTag>(url, { paginate: true, }) ).body.map((o) => o.name); diff --git a/lib/datasource/pod/__snapshots__/index.spec.ts.snap b/lib/datasource/pod/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000000..be773d988f --- /dev/null +++ b/lib/datasource/pod/__snapshots__/index.spec.ts.snap @@ -0,0 +1,107 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`datasource/cocoapods getReleases processes real data from CDN 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "host": "cdn.cocoapods.org", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://cdn.cocoapods.org/all_pods_versions_a_c_b.txt", + }, +] +`; + +exports[`datasource/cocoapods getReleases processes real data from Github 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/Artsy/Specs/contents/Specs/foo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/Artsy/Specs/contents/Specs/a/c/b/foo", + }, +] +`; + +exports[`datasource/cocoapods getReleases returns null for 401 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "host": "cdn.cocoapods.org", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://cdn.cocoapods.org/all_pods_versions_a_c_b.txt", + }, +] +`; + +exports[`datasource/cocoapods getReleases returns null for 404 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/foo/bar/contents/Specs/foo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/foo/bar/contents/Specs/a/c/b/foo", + }, +] +`; + +exports[`datasource/cocoapods getReleases returns null for unknown error 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "host": "cdn.cocoapods.org", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://cdn.cocoapods.org/all_pods_versions_a_c_b.txt", + }, +] +`; + +exports[`datasource/cocoapods getReleases throws for 429 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "host": "cdn.cocoapods.org", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://cdn.cocoapods.org/all_pods_versions_a_c_b.txt", + }, +] +`; diff --git a/lib/datasource/pod/index.spec.ts b/lib/datasource/pod/index.spec.ts index 49c6500254..5161bbcdeb 100644 --- a/lib/datasource/pod/index.spec.ts +++ b/lib/datasource/pod/index.spec.ts @@ -1,16 +1,10 @@ import { getPkgReleases } from '..'; -import { mocked } from '../../../test/util'; -import { GotResponse } from '../../platform'; -import { api as _api } from '../../platform/github/gh-got-wrapper'; +import * as httpMock from '../../../test/httpMock'; import * as globalCache from '../../util/cache/global'; import * as runCache from '../../util/cache/run'; import * as rubyVersioning from '../../versioning/ruby'; import * as pod from '.'; -const api = mocked(_api); - -jest.mock('../../platform/github/gh-got-wrapper'); - const config = { versioning: rubyVersioning.id, datasource: pod.id, @@ -18,16 +12,23 @@ const config = { registryUrls: [], }; +const githubApiHost = 'https://api.github.com'; +const cocoapodsHost = 'https://cdn.cocoapods.org'; + describe('datasource/cocoapods', () => { describe('getReleases', () => { beforeEach(() => { jest.resetAllMocks(); - runCache.clear(); + httpMock.setup(); return globalCache.rmAll(); }); + afterEach(() => { + httpMock.reset(); + runCache.clear(); + }); + it('returns null for invalid inputs', async () => { - api.get.mockResolvedValueOnce(null); expect( await getPkgReleases({ datasource: pod.id, @@ -37,77 +38,53 @@ describe('datasource/cocoapods', () => { ).toBeNull(); }); it('returns null for empty result', async () => { - api.get.mockResolvedValueOnce(null); - expect(await getPkgReleases(config)).toBeNull(); - }); - it('returns null for missing fields', async () => { - api.get.mockResolvedValueOnce({} as GotResponse); - expect(await getPkgReleases(config)).toBeNull(); - - api.get.mockResolvedValueOnce({ body: '' } as GotResponse); expect(await getPkgReleases(config)).toBeNull(); }); it('returns null for 404', async () => { - api.get.mockImplementation(() => - Promise.reject({ - statusCode: 404, - }) - ); - expect( - await getPkgReleases({ - ...config, - registryUrls: [ - ...config.registryUrls, - 'invalid', - 'https://github.com/foo/bar', - ], - }) - ).toBeNull(); + httpMock + .scope(githubApiHost) + .get('/repos/foo/bar/contents/Specs/foo') + .reply(404) + .get('/repos/foo/bar/contents/Specs/a/c/b/foo') + .reply(404); + const res = await getPkgReleases({ + ...config, + registryUrls: [...config.registryUrls, 'https://github.com/foo/bar'], + }); + expect(res).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns null for 401', async () => { - api.get.mockImplementationOnce(() => - Promise.reject({ - statusCode: 401, - }) - ); + httpMock + .scope(cocoapodsHost) + .get('/all_pods_versions_a_c_b.txt') + .reply(401); expect(await getPkgReleases(config)).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('throws for 429', async () => { - api.get.mockImplementationOnce(() => - Promise.reject({ - statusCode: 429, - }) - ); - await expect( - getPkgReleases({ - ...config, - registryUrls: ['https://cdn.cocoapods.org'], - }) - ).rejects.toThrowError('registry-failure'); - }); - it('throws for 5xx', async () => { - api.get.mockImplementationOnce(() => - Promise.reject({ - statusCode: 502, - }) + httpMock + .scope(cocoapodsHost) + .get('/all_pods_versions_a_c_b.txt') + .reply(429); + await expect(getPkgReleases(config)).rejects.toThrowError( + 'registry-failure' ); - await expect( - getPkgReleases({ - ...config, - registryUrls: ['https://cdn.cocoapods.org'], - }) - ).rejects.toThrowError('registry-failure'); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns null for unknown error', async () => { - api.get.mockImplementationOnce(() => { - throw new Error(); - }); + httpMock + .scope(cocoapodsHost) + .get('/all_pods_versions_a_c_b.txt') + .replyWithError('foobar'); expect(await getPkgReleases(config)).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('processes real data from CDN', async () => { - api.get.mockResolvedValueOnce({ - body: 'foo/1.2.3', - } as GotResponse); + httpMock + .scope(cocoapodsHost) + .get('/all_pods_versions_a_c_b.txt') + .reply(200, 'foo/1.2.3'); expect( await getPkgReleases({ ...config, @@ -120,23 +97,27 @@ describe('datasource/cocoapods', () => { }, ], }); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('processes real data from Github', async () => { - api.get.mockResolvedValueOnce({ - body: [{ name: '1.2.3' }], - } as GotResponse); - expect( - await getPkgReleases({ - ...config, - registryUrls: ['https://github.com/Artsy/Specs'], - }) - ).toEqual({ + httpMock + .scope(githubApiHost) + .get('/repos/Artsy/Specs/contents/Specs/foo') + .reply(404) + .get('/repos/Artsy/Specs/contents/Specs/a/c/b/foo') + .reply(200, [{ name: '1.2.3' }]); + const res = await getPkgReleases({ + ...config, + registryUrls: ['https://github.com/Artsy/Specs'], + }); + expect(res).toEqual({ releases: [ { version: '1.2.3', }, ], }); + expect(httpMock.getTrace()).toMatchSnapshot(); }); }); }); diff --git a/lib/datasource/pod/index.ts b/lib/datasource/pod/index.ts index e21cf5c768..ce957dad14 100644 --- a/lib/datasource/pod/index.ts +++ b/lib/datasource/pod/index.ts @@ -1,7 +1,8 @@ import crypto from 'crypto'; import { logger } from '../../logger'; -import { api } from '../../platform/github/gh-got-wrapper'; import * as globalCache from '../../util/cache/global'; +import { Http } from '../../util/http'; +import { GithubHttp } from '../../util/http/github'; import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'pod'; @@ -11,6 +12,9 @@ export const defaultRegistryUrls = ['https://cdn.cocoapods.org']; const cacheNamespace = `datasource-${id}`; const cacheMinutes = 30; +const githubHttp = new GithubHttp(); +const http = new Http(id); + function shardParts(lookupName: string): string[] { return crypto .createHash('md5') @@ -31,34 +35,53 @@ function releasesGithubUrl( return `${prefix}/${account}/${repo}/contents/Specs/${suffix}`; } -async function makeRequest<T = unknown>( +function handleError(lookupName: string, err: Error): void { + const errorData = { lookupName, err }; + + if ( + err.statusCode === 429 || + (err.statusCode >= 500 && err.statusCode < 600) + ) { + logger.warn({ lookupName, err }, `CocoaPods registry failure`); + throw new Error('registry-failure'); + } + + if (err.statusCode === 401) { + logger.debug(errorData, 'Authorization error'); + } else if (err.statusCode === 404) { + logger.debug(errorData, 'Package lookup error'); + } else { + logger.warn(errorData, 'CocoaPods lookup failure: Unknown error'); + } +} + +async function requestCDN( url: string, - lookupName: string, - json = true -): Promise<T | null> { + lookupName: string +): Promise<string | null> { try { - const resp = await api.get(url, { json }); + const resp = await http.get(url); if (resp && resp.body) { return resp.body; } } catch (err) { - const errorData = { lookupName, err }; - - if ( - err.statusCode === 429 || - (err.statusCode >= 500 && err.statusCode < 600) - ) { - logger.warn({ lookupName, err }, `CocoaPods registry failure`); - throw new Error('registry-failure'); - } + handleError(lookupName, err); + } - if (err.statusCode === 401) { - logger.debug(errorData, 'Authorization error'); - } else if (err.statusCode === 404) { - logger.debug(errorData, 'Package lookup error'); - } else { - logger.warn(errorData, 'CocoaPods lookup failure: Unknown error'); + return null; +} + +async function requestGithub<T = unknown>( + url: string, + lookupName: string +): Promise<T | null> { + try { + const resp = await githubHttp.getJson<T>(url); + if (resp && resp.body) { + return resp.body; } + } catch (err) { + handleError(lookupName, err); } return null; @@ -75,7 +98,7 @@ async function getReleasesFromGithub( const { account, repo } = (match && match.groups) || {}; const opts = { account, repo, useShard }; const url = releasesGithubUrl(lookupName, opts); - const resp = await makeRequest<{ name: string }[]>(url, lookupName); + const resp = await requestGithub<{ name: string }[]>(url, lookupName); if (resp) { const releases = resp.map(({ name }) => ({ version: name })); return { releases }; @@ -98,7 +121,7 @@ async function getReleasesFromCDN( registryUrl: string ): Promise<ReleaseResult | null> { const url = releasesCDNUrl(lookupName, registryUrl); - const resp = await makeRequest<string>(url, lookupName, false); + const resp = await requestCDN(url, lookupName); if (resp) { const lines = resp.split('\n'); for (let idx = 0; idx < lines.length; idx += 1) { diff --git a/lib/platform/github/__snapshots__/index.spec.ts.snap b/lib/platform/github/__snapshots__/index.spec.ts.snap index 9b6a588d17..9fc7c8a76b 100644 --- a/lib/platform/github/__snapshots__/index.spec.ts.snap +++ b/lib/platform/github/__snapshots__/index.spec.ts.snap @@ -271,50 +271,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "comments": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "body": null, - "databaseId": null, - }, - }, - "headRefName": null, - "number": null, - "state": null, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 512, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -394,50 +355,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "comments": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "body": null, - "databaseId": null, - }, - }, - "headRefName": null, - "number": null, - "state": null, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 512, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -517,50 +439,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "comments": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "body": null, - "databaseId": null, - }, - }, - "headRefName": null, - "number": null, - "state": null, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 512, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -629,50 +512,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "comments": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "body": null, - "databaseId": null, - }, - }, - "headRefName": null, - "number": null, - "state": null, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 512, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -738,50 +582,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "comments": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "body": null, - "databaseId": null, - }, - }, - "headRefName": null, - "number": null, - "state": null, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 512, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -847,50 +652,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "comments": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "body": null, - "databaseId": null, - }, - }, - "headRefName": null, - "number": null, - "state": null, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 512, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -967,50 +733,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "comments": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "body": null, - "databaseId": null, - }, - }, - "headRefName": null, - "number": null, - "state": null, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 512, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -1045,12 +772,36 @@ Array [ exports[`platform/github ensureIssue() closes others if ensuring only once 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1075,12 +826,36 @@ Array [ exports[`platform/github ensureIssue() creates issue 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1108,12 +883,36 @@ Array [ exports[`platform/github ensureIssue() creates issue if not ensuring only once 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1138,12 +937,36 @@ Array [ exports[`platform/github ensureIssue() creates issue if reopen flag false and issue is not open 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1182,12 +1005,36 @@ Array [ exports[`platform/github ensureIssue() deletes if duplicate 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1226,12 +1073,36 @@ Array [ exports[`platform/github ensureIssue() does not create issue if ensuring only once 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1245,12 +1116,36 @@ Array [ exports[`platform/github ensureIssue() does not create issue if reopen flag false and issue is already open 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1275,12 +1170,36 @@ Array [ exports[`platform/github ensureIssue() skips update if unchanged 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1305,12 +1224,36 @@ Array [ exports[`platform/github ensureIssue() updates issue 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1349,12 +1292,36 @@ Array [ exports[`platform/github ensureIssueClosing() closes issue 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1382,12 +1349,36 @@ Array [ exports[`platform/github findIssue() finds issue 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1412,12 +1403,36 @@ Array [ exports[`platform/github findIssue() returns null if no issue 1`] = ` Array [ Object { - "body": "{\\"query\\":\\"\\\\n query {\\\\n repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n pageInfo {\\\\n endCursor\\\\n hasNextPage\\\\n }\\\\n nodes {\\\\n number\\\\n state\\\\n title\\\\n body\\\\n }\\\\n }\\\\n }\\\\n }\\\\n \\"}", + "graphql": Object { + "query": Object { + "repository": Object { + "__args": Object { + "name": "undefined", + "owner": "undefined", + }, + "issues": Object { + "__args": Object { + "first": "100", + }, + "nodes": Object { + "body": null, + "number": null, + "state": null, + "title": null, + }, + "pageInfo": Object { + "endCursor": null, + "hasNextPage": null, + }, + }, + }, + }, + }, "headers": Object { "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 423, + "content-length": 425, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -1604,136 +1619,16 @@ Array [ }, }, "mergeStateStatus": null, - "mergeable": null, - "number": null, - "reviews": Object { - "__args": Object { - "first": "1", - }, - "nodes": Object { - "state": null, - }, - }, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept": "application/vnd.github.merge-info-preview+json", - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 1264, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "baseRefName": null, - "body": null, - "commits": Object { - "__args": Object { - "first": "2", - }, - "nodes": Object { - "commit": Object { - "author": Object { - "email": null, - }, - "committer": Object { - "email": null, - }, - "parents": Object { - "__args": Object { - "last": "1", - }, - "edges": Object { - "node": Object { - "abbreviatedOid": null, - "oid": null, - }, - }, - }, - }, - }, - }, - "headRefName": null, - "labels": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "name": null, - }, - }, - "mergeStateStatus": null, - "mergeable": null, - "number": null, - "reviews": Object { - "__args": Object { - "first": "1", - }, - "nodes": Object { - "state": null, - }, - }, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept": "application/vnd.github.merge-info-preview+json", - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 1263, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "100", - }, - "nodes": Object { - "comments": Object { + "mergeable": null, + "number": null, + "reviews": Object { "__args": Object { - "last": "100", + "first": "1", }, "nodes": Object { - "body": null, - "databaseId": null, + "state": null, }, }, - "headRefName": null, - "number": null, - "state": null, "title": null, }, }, @@ -1741,9 +1636,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 513, + "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -1760,7 +1657,7 @@ Array [ }, "pullRequests": Object { "__args": Object { - "first": "25", + "first": "100", }, "nodes": Object { "comments": Object { @@ -1782,9 +1679,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 512, + "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -1985,84 +1884,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "baseRefName": null, - "body": null, - "commits": Object { - "__args": Object { - "first": "2", - }, - "nodes": Object { - "commit": Object { - "author": Object { - "email": null, - }, - "committer": Object { - "email": null, - }, - "parents": Object { - "__args": Object { - "last": "1", - }, - "edges": Object { - "node": Object { - "abbreviatedOid": null, - "oid": null, - }, - }, - }, - }, - }, - }, - "headRefName": null, - "labels": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "name": null, - }, - }, - "mergeStateStatus": null, - "mergeable": null, - "number": null, - "reviews": Object { - "__args": Object { - "first": "1", - }, - "nodes": Object { - "state": null, - }, - }, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept": "application/vnd.github.merge-info-preview+json", - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 1263, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -2101,50 +1923,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "comments": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "body": null, - "databaseId": null, - }, - }, - "headRefName": null, - "number": null, - "state": null, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 512, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -2473,136 +2256,58 @@ Array [ "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, - "method": "GET", - "url": "https://api.github.com/repos/some/repo", - }, - Object { - "headers": Object { - "accept": "application/json", - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "GET", - "url": "https://api.github.com/repos/some/repo/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e/statuses", - }, -] -`; - -exports[`platform/github getPr(prNo) should return PR from closed graphql result 1`] = ` -Object { - "body": "dummy body", - "branchName": "renovate/delay-4.x", - "comments": Array [ - Object { - "body": ":tada: This PR is included in version 13.63.5 :tada: - -The release is available on: -- [npm package (@latest dist-tag)](https://www.npmjs.com/package/renovate) -- [GitHub release](https://github.com/renovatebot/renovate/releases/tag/13.63.5) - -Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:", - "id": 420006957, - }, - ], - "displayNumber": "Pull Request #2499", - "number": 2499, - "state": "merged", - "title": "build(deps): update dependency delay to v4.0.1", -} -`; - -exports[`platform/github getPr(prNo) should return PR from closed graphql result 2`] = ` -Array [ - Object { - "headers": Object { - "accept": "application/json", - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "GET", - "url": "https://api.github.com/repos/some/repo", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "100", - }, - "nodes": Object { - "baseRefName": null, - "body": null, - "commits": Object { - "__args": Object { - "first": "2", - }, - "nodes": Object { - "commit": Object { - "author": Object { - "email": null, - }, - "committer": Object { - "email": null, - }, - "parents": Object { - "__args": Object { - "last": "1", - }, - "edges": Object { - "node": Object { - "abbreviatedOid": null, - "oid": null, - }, - }, - }, - }, - }, - }, - "headRefName": null, - "labels": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "name": null, - }, - }, - "mergeStateStatus": null, - "mergeable": null, - "number": null, - "reviews": Object { - "__args": Object { - "first": "1", - }, - "nodes": Object { - "state": null, - }, - }, - "title": null, - }, - }, - }, - }, + "method": "GET", + "url": "https://api.github.com/repos/some/repo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc123", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/repo/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e/statuses", + }, +] +`; + +exports[`platform/github getPr(prNo) should return PR from closed graphql result 1`] = ` +Object { + "body": "dummy body", + "branchName": "renovate/delay-4.x", + "comments": Array [ + Object { + "body": ":tada: This PR is included in version 13.63.5 :tada: + +The release is available on: +- [npm package (@latest dist-tag)](https://www.npmjs.com/package/renovate) +- [GitHub release](https://github.com/renovatebot/renovate/releases/tag/13.63.5) + +Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:", + "id": 420006957, }, + ], + "displayNumber": "Pull Request #2499", + "number": 2499, + "state": "merged", + "title": "build(deps): update dependency delay to v4.0.1", +} +`; + +exports[`platform/github getPr(prNo) should return PR from closed graphql result 2`] = ` +Array [ + Object { "headers": Object { - "accept": "application/vnd.github.merge-info-preview+json", + "accept": "application/json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 1264, "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, - "method": "POST", - "url": "https://api.github.com/graphql", + "method": "GET", + "url": "https://api.github.com/repos/some/repo", }, Object { "graphql": Object { @@ -2614,7 +2319,7 @@ Array [ }, "pullRequests": Object { "__args": Object { - "first": "25", + "first": "100", }, "nodes": Object { "baseRefName": null, @@ -2675,7 +2380,8 @@ Array [ "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 1263, + "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -2683,45 +2389,15 @@ Array [ "url": "https://api.github.com/graphql", }, Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "100", - }, - "nodes": Object { - "comments": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "body": null, - "databaseId": null, - }, - }, - "headRefName": null, - "number": null, - "state": null, - "title": null, - }, - }, - }, - }, - }, "headers": Object { + "accept": "application/json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 513, "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, - "method": "POST", - "url": "https://api.github.com/graphql", + "method": "GET", + "url": "https://api.github.com/repos/some/repo/git/refs/heads/master", }, Object { "graphql": Object { @@ -2733,7 +2409,7 @@ Array [ }, "pullRequests": Object { "__args": Object { - "first": "25", + "first": "100", }, "nodes": Object { "comments": Object { @@ -2755,9 +2431,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 512, + "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -2868,84 +2546,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "repo", - "owner": "some", - }, - "pullRequests": Object { - "__args": Object { - "first": "25", - }, - "nodes": Object { - "baseRefName": null, - "body": null, - "commits": Object { - "__args": Object { - "first": "2", - }, - "nodes": Object { - "commit": Object { - "author": Object { - "email": null, - }, - "committer": Object { - "email": null, - }, - "parents": Object { - "__args": Object { - "last": "1", - }, - "edges": Object { - "node": Object { - "abbreviatedOid": null, - "oid": null, - }, - }, - }, - }, - }, - }, - "headRefName": null, - "labels": Object { - "__args": Object { - "last": "100", - }, - "nodes": Object { - "name": null, - }, - }, - "mergeStateStatus": null, - "mergeable": null, - "number": null, - "reviews": Object { - "__args": Object { - "first": "1", - }, - "nodes": Object { - "state": null, - }, - }, - "title": null, - }, - }, - }, - }, - }, - "headers": Object { - "accept": "application/vnd.github.merge-info-preview+json", - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 1263, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3064,6 +2665,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3102,9 +2704,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3229,6 +2833,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3267,9 +2872,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3404,6 +3011,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3442,9 +3050,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3580,6 +3190,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3618,9 +3229,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3767,6 +3380,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3805,9 +3419,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3954,6 +3570,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -3992,9 +3609,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4141,6 +3760,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4179,9 +3799,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4328,6 +3950,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4366,9 +3989,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4496,6 +4121,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 1264, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4534,9 +4160,11 @@ Array [ }, }, "headers": Object { + "accept": "application/vnd.github.merge-info-preview+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 513, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4759,6 +4387,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 697, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4820,6 +4449,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 697, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4881,6 +4511,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 697, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -4942,62 +4573,7 @@ Array [ "accept-encoding": "gzip, deflate", "authorization": "token abc123", "content-length": 697, - "host": "api.github.com", - "user-agent": "https://github.com/renovatebot/renovate", - }, - "method": "POST", - "url": "https://api.github.com/graphql", - }, - Object { - "graphql": Object { - "query": Object { - "repository": Object { - "__args": Object { - "name": "undefined", - "owner": "undefined", - }, - "vulnerabilityAlerts": Object { - "__args": Object { - "last": "100", - }, - "edges": Object { - "node": Object { - "dismissReason": null, - "securityAdvisory": Object { - "description": null, - "identifiers": Object { - "type": null, - "value": null, - }, - "references": Object { - "url": null, - }, - "severity": null, - }, - "securityVulnerability": Object { - "firstPatchedVersion": Object { - "identifier": null, - }, - "package": Object { - "ecosystem": null, - "name": null, - }, - "vulnerableVersionRange": null, - }, - "vulnerableManifestFilename": null, - "vulnerableManifestPath": null, - "vulnerableRequirements": null, - }, - }, - }, - }, - }, - }, - "headers": Object { - "accept": "application/vnd.github.vixen-preview+json", - "accept-encoding": "gzip, deflate", - "authorization": "token abc123", - "content-length": 697, + "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", }, diff --git a/lib/platform/github/gh-got-wrapper.spec.ts b/lib/platform/github/gh-got-wrapper.spec.ts deleted file mode 100644 index 9bb82f78f0..0000000000 --- a/lib/platform/github/gh-got-wrapper.spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -import delay from 'delay'; -import { Response } from 'got'; -import { - PLATFORM_BAD_CREDENTIALS, - PLATFORM_FAILURE, - PLATFORM_INTEGRATION_UNAUTHORIZED, - PLATFORM_RATE_LIMIT_EXCEEDED, - REPOSITORY_CHANGED, -} from '../../constants/error-messages'; -import _got from '../../util/got'; -import { api } from './gh-got-wrapper'; - -jest.mock('../../util/got'); -jest.mock('delay'); - -const got: any = _got; - -const get: <T extends object = any>( - path: string, - options?: any, - okToRetry?: boolean -) => Promise<Response<T>> = api as any; - -async function getError(): Promise<Error> { - try { - await get('some-url', {}, false); - } catch (err) { - return err; - } - return null; -} - -describe('platform/gh-got-wrapper', () => { - beforeEach(() => { - jest.resetAllMocks(); - delete global.appMode; - (delay as any).mockImplementation(() => Promise.resolve()); - }); - it('supports app mode', async () => { - global.appMode = true; - await api.get('some-url', { headers: { accept: 'some-accept' } }); - expect(got.mock.calls[0][1].headers.accept).toBe( - 'application/vnd.github.machine-man-preview+json, some-accept' - ); - }); - it('strips v3 for graphql', async () => { - got.mockImplementationOnce(() => ({ - body: '{"data":{', - })); - api.setBaseUrl('https://ghe.mycompany.com/api/v3/'); - await api.post('graphql', { - body: 'abc', - }); - expect(got.mock.calls[0][0].includes('/v3')).toBe(false); - }); - it('paginates', async () => { - got.mockReturnValueOnce({ - headers: { - link: - '<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel="next", <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=3>; rel="last"', - }, - body: ['a'], - }); - got.mockReturnValueOnce({ - headers: { - link: - '<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=3>; rel="next", <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=3>; rel="last"', - }, - body: ['b', 'c'], - }); - got.mockReturnValueOnce({ - headers: {}, - body: ['d'], - }); - const res = await api.get('some-url', { paginate: true }); - expect(res.body).toEqual(['a', 'b', 'c', 'd']); - expect(got).toHaveBeenCalledTimes(3); - }); - it('attempts to paginate', async () => { - got.mockReturnValueOnce({ - headers: { - link: - '<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last"', - }, - body: ['a'], - }); - got.mockReturnValueOnce({ - headers: {}, - body: ['b'], - }); - const res = await api.get('some-url', { paginate: true }); - expect(res.body).toHaveLength(1); - expect(got).toHaveBeenCalledTimes(1); - }); - it('should throw rate limit exceeded', async () => { - got.mockImplementationOnce(() => - Promise.reject({ - statusCode: 403, - message: - 'Error updating branch: API rate limit exceeded for installation ID 48411. (403)', - }) - ); - await expect(api.get('some-url')).rejects.toThrow(); - }); - it('should throw Bad credentials', async () => { - got.mockImplementationOnce(() => - Promise.reject({ - statusCode: 401, - message: 'Bad credentials. (401)', - }) - ); - const e = await getError(); - expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_BAD_CREDENTIALS); - }); - it('should throw platform failure', async () => { - got.mockImplementationOnce(() => - Promise.reject({ - statusCode: 401, - message: 'Bad credentials. (401)', - headers: { - 'x-ratelimit-limit': '60', - }, - }) - ); - const e = await getError(); - expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); - }); - it('should throw platform failure for ENOTFOUND, ETIMEDOUT or EAI_AGAIN', async () => { - const codes = ['ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN']; - for (let idx = 0; idx < codes.length; idx += 1) { - const code = codes[idx]; - got.mockImplementationOnce(() => - Promise.reject({ - name: 'RequestError', - code, - }) - ); - const e = await getError(); - expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); - } - }); - it('should throw platform failure for 500', async () => { - got.mockImplementationOnce(() => - Promise.reject({ - statusCode: 500, - message: 'Internal Server Error', - }) - ); - const e = await getError(); - expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); - }); - it('should throw platform failure ParseError', async () => { - got.mockImplementationOnce(() => - Promise.reject({ - name: 'ParseError', - }) - ); - const e = await getError(); - expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); - }); - it('should throw for unauthorized integration', async () => { - got.mockImplementationOnce(() => - Promise.reject({ - statusCode: 403, - message: 'Resource not accessible by integration (403)', - }) - ); - const e = await getError(); - expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_INTEGRATION_UNAUTHORIZED); - }); - it('should throw for unauthorized integration', async () => { - const gotErr = { - statusCode: 403, - body: { message: 'Upgrade to GitHub Pro' }, - }; - got.mockRejectedValueOnce(gotErr); - const e = await getError(); - expect(e).toBeDefined(); - expect(e).toBe(gotErr); - }); - it('should throw on abuse', async () => { - const gotErr = { - statusCode: 403, - message: 'You have triggered an abuse detection mechanism', - }; - got.mockRejectedValueOnce(gotErr); - const e = await getError(); - expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_RATE_LIMIT_EXCEEDED); - }); - it('should throw on repository change', async () => { - const gotErr = { - statusCode: 422, - body: { - message: 'foobar', - errors: [{ code: 'invalid' }], - }, - }; - got.mockRejectedValueOnce(gotErr); - const e = await getError(); - expect(e).toBeDefined(); - expect(e.message).toEqual(REPOSITORY_CHANGED); - }); - it('should throw platform failure on 422 response', async () => { - const gotErr = { - statusCode: 422, - message: 'foobar', - }; - got.mockRejectedValueOnce(gotErr); - const e = await getError(); - expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); - }); - it('should throw original error when failed to add reviewers', async () => { - const gotErr = { - statusCode: 422, - message: 'Review cannot be requested from pull request author.', - }; - got.mockRejectedValueOnce(gotErr); - const e = await getError(); - expect(e).toBeDefined(); - expect(e).toStrictEqual(gotErr); - }); - it('should throw original error of unknown type', async () => { - const gotErr = { - statusCode: 418, - message: 'Sorry, this is a teapot', - }; - got.mockRejectedValueOnce(gotErr); - const e = await getError(); - expect(e).toBe(gotErr); - }); -}); diff --git a/lib/platform/github/gh-got-wrapper.ts b/lib/platform/github/gh-got-wrapper.ts deleted file mode 100644 index b6e1e94dbc..0000000000 --- a/lib/platform/github/gh-got-wrapper.ts +++ /dev/null @@ -1,214 +0,0 @@ -import URL from 'url'; -import { GotError } from 'got'; -import pAll from 'p-all'; -import parseLinkHeader from 'parse-link-header'; - -import { - PLATFORM_BAD_CREDENTIALS, - PLATFORM_FAILURE, - PLATFORM_INTEGRATION_UNAUTHORIZED, - PLATFORM_RATE_LIMIT_EXCEEDED, - REPOSITORY_CHANGED, -} from '../../constants/error-messages'; -import { PLATFORM_TYPE_GITHUB } from '../../constants/platforms'; -import { logger } from '../../logger'; -import got, { GotJSONOptions } from '../../util/got'; -import { maskToken } from '../../util/mask'; -import { GotApi, GotResponse } from '../common'; - -const hostType = PLATFORM_TYPE_GITHUB; -export const getHostType = (): string => hostType; - -let baseUrl = 'https://api.github.com/'; -export const getBaseUrl = (): string => baseUrl; - -type GotRequestError<E = unknown, T = unknown> = GotError & { - body: { - message?: string; - errors?: E[]; - }; - headers?: Record<string, T>; -}; - -type GotRequestOptions = GotJSONOptions & { - token?: string; -}; - -export function dispatchError( - err: GotRequestError, - path: string, - opts: GotRequestOptions -): never { - let message = err.message; - if (err.body && err.body.message) { - message = err.body.message; - } - if ( - err.name === 'RequestError' && - (err.code === 'ENOTFOUND' || - err.code === 'ETIMEDOUT' || - err.code === 'EAI_AGAIN') - ) { - logger.debug({ err }, 'GitHub failure: RequestError'); - throw new Error(PLATFORM_FAILURE); - } - if (err.name === 'ParseError') { - logger.debug({ err }, 'GitHub failure: ParseError'); - throw new Error(PLATFORM_FAILURE); - } - if (err.statusCode >= 500 && err.statusCode < 600) { - logger.debug({ err }, 'GitHub failure: 5xx'); - throw new Error(PLATFORM_FAILURE); - } - if ( - err.statusCode === 403 && - message.startsWith('You have triggered an abuse detection mechanism') - ) { - logger.debug({ err }, 'GitHub failure: abuse detection'); - throw new Error(PLATFORM_RATE_LIMIT_EXCEEDED); - } - if (err.statusCode === 403 && message.includes('Upgrade to GitHub Pro')) { - logger.debug({ path }, 'Endpoint needs paid GitHub plan'); - throw err; - } - if (err.statusCode === 403 && message.includes('rate limit exceeded')) { - logger.debug({ err }, 'GitHub failure: rate limit'); - throw new Error(PLATFORM_RATE_LIMIT_EXCEEDED); - } - if ( - err.statusCode === 403 && - message.startsWith('Resource not accessible by integration') - ) { - logger.debug( - { err }, - 'GitHub failure: Resource not accessible by integration' - ); - throw new Error(PLATFORM_INTEGRATION_UNAUTHORIZED); - } - if (err.statusCode === 401 && message.includes('Bad credentials')) { - const rateLimit = err.headers ? err.headers['x-ratelimit-limit'] : -1; - logger.debug( - { - token: maskToken(opts.token), - err, - }, - 'GitHub failure: Bad credentials' - ); - if (rateLimit === '60') { - throw new Error(PLATFORM_FAILURE); - } - throw new Error(PLATFORM_BAD_CREDENTIALS); - } - if (err.statusCode === 422) { - if ( - message.includes('Review cannot be requested from pull request author') - ) { - throw err; - } else if ( - err.body && - err.body.errors && - err.body.errors.find((e: any) => e.code === 'invalid') - ) { - throw new Error(REPOSITORY_CHANGED); - } - logger.debug({ err }, '422 Error thrown from GitHub'); - throw new Error(PLATFORM_FAILURE); - } - throw err; -} - -async function get( - path: string, - options?: any, - okToRetry = true -): Promise<GotResponse> { - let result = null; - - const opts = { - hostType, - baseUrl, - json: true, - ...options, - }; - const method = opts.method || 'get'; - if (method.toLowerCase() === 'post' && path === 'graphql') { - // GitHub Enterprise uses unversioned graphql path - opts.baseUrl = opts.baseUrl.replace('/v3/', '/'); - } - logger.trace(`${method.toUpperCase()} ${path}`); - try { - if (global.appMode) { - const appAccept = 'application/vnd.github.machine-man-preview+json'; - opts.headers = { - accept: appAccept, - 'user-agent': - process.env.RENOVATE_USER_AGENT || - 'https://github.com/renovatebot/renovate', - ...opts.headers, - }; - if (opts.headers.accept !== appAccept) { - opts.headers.accept = `${appAccept}, ${opts.headers.accept}`; - } - } - result = await got(path, opts); - if (opts.paginate) { - // Check if result is paginated - const pageLimit = opts.pageLimit || 10; - const linkHeader = parseLinkHeader(result.headers.link as string); - if (linkHeader && linkHeader.next && linkHeader.last) { - let lastPage = +linkHeader.last.page; - if (!process.env.RENOVATE_PAGINATE_ALL && opts.paginate !== 'all') { - lastPage = Math.min(pageLimit, lastPage); - } - const pageNumbers = Array.from( - new Array(lastPage), - (x, i) => i + 1 - ).slice(1); - const queue = pageNumbers.map((page) => (): Promise<GotResponse> => { - const nextUrl = URL.parse(linkHeader.next.url, true); - delete nextUrl.search; - nextUrl.query.page = page.toString(); - return get( - URL.format(nextUrl), - { ...opts, paginate: false }, - okToRetry - ); - }); - const pages = await pAll<{ body: any[] }>(queue, { concurrency: 5 }); - result.body = result.body.concat( - ...pages.filter(Boolean).map((page) => page.body) - ); - } - } - // istanbul ignore if - if (method === 'POST' && path === 'graphql') { - const goodResult = '{"data":{'; - if (result.body.startsWith(goodResult)) { - if (!okToRetry) { - logger.debug('Recovered graphql query'); - } - } else if (okToRetry) { - logger.debug('Retrying graphql query'); - opts.body = opts.body.replace('first: 100', 'first: 25'); - return get(path, opts, !okToRetry); - } - } - } catch (gotErr) { - dispatchError(gotErr, path, opts); - } - return result; -} - -const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete']; - -for (const x of helpers) { - (get as any)[x] = (url: string, opts: any): Promise<GotResponse> => - get(url, { ...opts, method: x.toUpperCase() }); -} - -get.setBaseUrl = (u: string): void => { - baseUrl = u; -}; - -export const api: GotApi = get as any; -export default api; diff --git a/lib/platform/github/gh-graphql-wrapper.spec.ts b/lib/platform/github/gh-graphql-wrapper.spec.ts deleted file mode 100644 index d3a0626eaf..0000000000 --- a/lib/platform/github/gh-graphql-wrapper.spec.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { getGraphqlNodes } from './gh-graphql-wrapper'; - -/** @type any */ -const got = require('../../util/got').default; - -jest.mock('../../util/got'); - -const query = ` - query { - repository(owner: "testOwner", name: "testName") { - testItem (orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: "someone"}) { - pageInfo { - endCursor - hasNextPage - } - nodes { - number state title body - } - } - } - }`; - -async function getError(q: string, f: string) { - let error; - try { - await getGraphqlNodes(q, f); - } catch (err) { - error = err; - } - return error; -} - -describe('platform/gh-graphql-wrapper', () => { - beforeEach(() => { - jest.resetAllMocks(); - delete global.appMode; - }); - it('supports app mode', async () => { - global.appMode = true; - await getGraphqlNodes(query, 'testItem'); - expect(got.mock.calls[0][1].headers.accept).toEqual( - 'application/vnd.github.machine-man-preview+json, application/vnd.github.merge-info-preview+json' - ); - }); - it('returns empty array for undefined data', async () => { - got.mockReturnValue({ - body: { - data: { - someprop: 'someval', - }, - }, - }); - expect(await getGraphqlNodes(query, 'testItem')).toEqual([]); - }); - it('returns empty array for undefined data.', async () => { - got.mockReturnValue({ - body: { - data: { repository: { otherField: 'someval' } }, - }, - }); - expect(await getGraphqlNodes(query, 'testItem')).toEqual([]); - }); - it('throws errors for invalid responses', async () => { - const gotErr = { - statusCode: 418, - message: 'Sorry, this is a teapot', - }; - got.mockImplementationOnce(() => Promise.reject(gotErr)); - const e = await getError(query, 'someItem'); - expect(e).toBe(gotErr); - }); - it('halves node count and retries request', async () => { - got.mockReturnValue({ - body: { - data: { - someprop: 'someval', - }, - }, - }); - - await getGraphqlNodes(query, 'testItem'); - expect(got).toHaveBeenCalledTimes(7); - }); - it('retrieves all data from all pages', async () => { - got.mockReturnValueOnce({ - body: { - data: { - repository: { - testItem: { - pageInfo: { - endCursor: 'cursor1', - hasNextPage: true, - }, - nodes: [ - { - number: 1, - state: 'OPEN', - title: 'title-1', - body: 'the body 1', - }, - ], - }, - }, - }, - }, - }); - - got.mockReturnValueOnce({ - body: { - data: { - repository: { - testItem: { - pageInfo: { - endCursor: 'cursor2', - hasNextPage: true, - }, - nodes: [ - { - number: 2, - state: 'CLOSED', - title: 'title-2', - body: 'the body 2', - }, - ], - }, - }, - }, - }, - }); - - got.mockReturnValueOnce({ - body: { - data: { - repository: { - testItem: { - pageInfo: { - endCursor: 'cursor3', - hasNextPage: false, - }, - nodes: [ - { - number: 3, - state: 'OPEN', - title: 'title-3', - body: 'the body 3', - }, - ], - }, - }, - }, - }, - }); - - const items = await getGraphqlNodes(query, 'testItem'); - expect(got).toHaveBeenCalledTimes(3); - expect(items.length).toEqual(3); - }); -}); diff --git a/lib/platform/github/gh-graphql-wrapper.ts b/lib/platform/github/gh-graphql-wrapper.ts deleted file mode 100644 index 9b63e4824a..0000000000 --- a/lib/platform/github/gh-graphql-wrapper.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { logger } from '../../logger'; -import got, { GotJSONOptions } from '../../util/got'; -import { dispatchError, getBaseUrl, getHostType } from './gh-got-wrapper'; - -const accept = 'application/vnd.github.merge-info-preview+json'; - -const gqlOpts: GotJSONOptions = { - json: true, - method: 'POST', - headers: { - accept, - }, -}; - -interface GithubGraphqlResponse<T = unknown> { - data?: { - repository?: T; - }; - errors?: { message: string; locations: unknown }[]; -} - -async function get<T = unknown>( - query: string -): Promise<GithubGraphqlResponse<T>> { - let result = null; - - const path = 'graphql'; - const options: GotJSONOptions = { - ...gqlOpts, - hostType: getHostType(), - baseUrl: (getBaseUrl() || '').replace('/v3/', '/'), // GitHub Enterprise uses unversioned graphql path - body: { query }, - }; - - if (global.appMode) { - options.headers = { - ...options.headers, - accept: `application/vnd.github.machine-man-preview+json, ${accept}`, - 'user-agent': - process.env.RENOVATE_USER_AGENT || - 'https://github.com/renovatebot/renovate', - }; - } - - logger.trace(`Performing Github GraphQL request`); - - try { - const res = await got('graphql', options); - result = res && res.body; - } catch (gotErr) { - dispatchError(gotErr, path, options); - } - return result; -} - -export async function getGraphqlNodes<T = Record<string, unknown>>( - queryOrig: string, - fieldName: string -): Promise<T[]> { - const result: T[] = []; - - const regex = new RegExp(`(\\W)${fieldName}(\\s*)\\(`); - - let cursor = null; - let count = 100; - let canIterate = true; - - while (canIterate) { - let replacement = `$1${fieldName}$2(first: ${count}`; - if (cursor) { - replacement += `, after: "${cursor}", `; - } - const query = queryOrig.replace(regex, replacement); - const gqlRes = await get<T>(query); - if ( - gqlRes && - gqlRes.data && - gqlRes.data.repository && - gqlRes.data.repository[fieldName] - ) { - const { nodes, pageInfo } = gqlRes.data.repository[fieldName]; - result.push(...nodes); - - const { hasNextPage, endCursor } = pageInfo; - if (hasNextPage && endCursor) { - cursor = endCursor; - } else { - canIterate = false; - } - } else { - count = Math.floor(count / 2); - if (count === 0) { - logger.error('Error fetching GraphQL nodes'); - canIterate = false; - } - } - } - - return result; -} diff --git a/lib/platform/github/index.spec.ts b/lib/platform/github/index.spec.ts index f617748be4..9234301c2f 100644 --- a/lib/platform/github/index.spec.ts +++ b/lib/platform/github/index.spec.ts @@ -64,7 +64,7 @@ describe('platform/github', () => { 'lib/platform/github/__fixtures__/graphql/pullrequest-1.json', 'utf8' ); - const graphqlClosedPullrequests = fs.readFileSync( + const graphqlClosedPullRequests = fs.readFileSync( 'lib/platform/github/__fixtures__/graphql/pullrequests-closed.json', 'utf8' ); @@ -426,8 +426,8 @@ describe('platform/github', () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); scope - .persist() .post('/graphql') + .twice() .reply(200, {}) .get('/repos/some/repo/pulls?per_page=100&state=all') .reply(200, [ @@ -468,8 +468,8 @@ describe('platform/github', () => { const scope = httpMock.scope(githubApiHost); forkInitRepoMock(scope, 'some/repo', 'forked/repo'); scope - .persist() .post('/graphql') + .twice() .reply(200, {}) .get('/repos/some/repo/pulls?per_page=100&state=all') .reply(200, [ @@ -1329,7 +1329,6 @@ describe('platform/github', () => { initRepoMock(scope, 'some/repo'); scope .post('/graphql') - .twice() .reply(200, {}) .get('/repos/some/repo/issues/42/comments?per_page=100') .reply(200, []) @@ -1351,8 +1350,7 @@ describe('platform/github', () => { initRepoMock(scope, 'some/repo'); scope .post('/graphql') - .twice() - .reply(200, graphqlClosedPullrequests) + .reply(200, graphqlClosedPullRequests) .post('/repos/some/repo/issues/2499/comments') .reply(200); await github.initRepo({ @@ -1370,7 +1368,6 @@ describe('platform/github', () => { initRepoMock(scope, 'some/repo'); scope .post('/graphql') - .twice() .reply(200, {}) .get('/repos/some/repo/issues/42/comments?per_page=100') .reply(200, [{ id: 1234, body: '### some-subject\n\nblablabla' }]) @@ -1391,7 +1388,6 @@ describe('platform/github', () => { initRepoMock(scope, 'some/repo'); scope .post('/graphql') - .twice() .reply(200, {}) .get('/repos/some/repo/issues/42/comments?per_page=100') .reply(200, [{ id: 1234, body: '### some-subject\n\nsome\ncontent' }]); @@ -1410,7 +1406,6 @@ describe('platform/github', () => { initRepoMock(scope, 'some/repo'); scope .post('/graphql') - .twice() .reply(200, {}) .get('/repos/some/repo/issues/42/comments?per_page=100') .reply(200, [{ id: 1234, body: '!merge' }]); @@ -1431,7 +1426,6 @@ describe('platform/github', () => { initRepoMock(scope, 'some/repo'); scope .post('/graphql') - .twice() .reply(200, {}) .get('/repos/some/repo/issues/42/comments?per_page=100') .reply(200, [{ id: 1234, body: '### some-subject\n\nblablabla' }]) @@ -1446,7 +1440,6 @@ describe('platform/github', () => { initRepoMock(scope, 'some/repo'); scope .post('/graphql') - .twice() .reply(200, {}) .get('/repos/some/repo/issues/42/comments?per_page=100') .reply(200, [{ id: 1234, body: 'some-content' }]) @@ -1596,7 +1589,6 @@ describe('platform/github', () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); scope - .persist() .post('/graphql') .reply(200, graphqlOpenPullRequests) .get('/repos/some/repo/git/refs/heads/master') @@ -1624,11 +1616,13 @@ describe('platform/github', () => { .post('/graphql') .reply(200, graphqlOpenPullRequests) .post('/graphql') - .times(2) - .reply(200, {}) - - .post('/graphql') - .reply(200, graphqlClosedPullrequests); + .reply(200, graphqlClosedPullRequests) + .get('/repos/some/repo/git/refs/heads/master') + .reply(200, { + object: { + sha: '1234123412341234123412341234123412341234', + }, + }); await github.initRepo({ repository: 'some/repo', } as any); @@ -2108,28 +2102,43 @@ describe('platform/github', () => { }); describe('getVulnerabilityAlerts()', () => { it('returns empty if error', async () => { - httpMock.scope(githubApiHost).post('/graphql').twice().reply(200, {}); + httpMock.scope(githubApiHost).post('/graphql').reply(200, {}); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(0); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns array if found', async () => { // prettier-ignore - httpMock.scope(githubApiHost).post('/graphql').reply(200, "{\"data\":{\"repository\":{\"vulnerabilityAlerts\":{\"edges\":[{\"node\":{\"externalIdentifier\":\"CVE-2018-1000136\",\"externalReference\":\"https://nvd.nist.gov/vuln/detail/CVE-2018-1000136\",\"affectedRange\":\">= 1.8, < 1.8.3\",\"fixedIn\":\"1.8.3\",\"id\":\"MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQ1MzE3NDk4MQ==\",\"packageName\":\"electron\"}}]}}}}"); + httpMock.scope(githubApiHost).post('/graphql').reply(200, { + "data": { + "repository": { + "vulnerabilityAlerts": { + "edges": [{ + "node": { + "externalIdentifier": "CVE-2018-1000136", + "externalReference": "https://nvd.nist.gov/vuln/detail/CVE-2018-1000136", + "affectedRange": ">= 1.8, < 1.8.3", "fixedIn": "1.8.3", + "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQ1MzE3NDk4MQ==", "packageName": "electron" + } + }] + } + } + } + }); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(1); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns empty if disabled', async () => { // prettier-ignore - httpMock.scope(githubApiHost).post('/graphql').reply(200, "{\"data\":{\"repository\":{}}}"); + httpMock.scope(githubApiHost).post('/graphql').reply(200, {data: { repository: {} }} ); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(0); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('handles network error', async () => { // prettier-ignore - httpMock.scope(githubApiHost).persist().post('/graphql').replyWithError('unknown error'); + httpMock.scope(githubApiHost).post('/graphql').replyWithError('unknown error'); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(0); expect(httpMock.getTrace()).toMatchSnapshot(); diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts index 3ad40973f2..93da5144f3 100644 --- a/lib/platform/github/index.ts +++ b/lib/platform/github/index.ts @@ -25,6 +25,7 @@ import { import { logger } from '../../logger'; import { BranchStatus } from '../../types'; import * as hostRules from '../../util/host-rules'; +import * as githubHttp from '../../util/http/github'; import { sanitize } from '../../util/sanitize'; import { ensureTrailingSlash } from '../../util/url'; import { @@ -45,8 +46,6 @@ import { } from '../common'; import GitStorage, { StatusResult } from '../git/storage'; import { smartTruncate } from '../utils/pr-body'; -import { api } from './gh-got-wrapper'; -import { getGraphqlNodes } from './gh-graphql-wrapper'; import { BranchProtection, CombinedBranchStatus, @@ -57,6 +56,8 @@ import { PrList, } from './types'; +const githubApi = new githubHttp.GithubHttp(); + const defaultConfigFile = configFileNames[0]; let config: LocalRepoConfig = {} as any; @@ -82,7 +83,7 @@ export async function initPlatform({ if (endpoint) { defaults.endpoint = ensureTrailingSlash(endpoint); - api.setBaseUrl(defaults.endpoint); + githubHttp.setBaseUrl(defaults.endpoint); } else { logger.debug('Using default github endpoint: ' + defaults.endpoint); } @@ -90,9 +91,12 @@ export async function initPlatform({ let renovateUsername: string; try { const userData = ( - await api.get(defaults.endpoint + 'user', { - token, - }) + await githubApi.getJson<{ login: string; name: string }>( + defaults.endpoint + 'user', + { + token, + } + ) ).body; renovateUsername = userData.login; gitAuthor = userData.name; @@ -102,9 +106,12 @@ export async function initPlatform({ } try { const userEmail = ( - await api.get(defaults.endpoint + 'user/emails', { - token, - }) + await githubApi.getJson<{ email: string }[]>( + defaults.endpoint + 'user/emails', + { + token, + } + ) ).body; if (userEmail.length && userEmail[0].email) { gitAuthor += ` <${userEmail[0].email}>`; @@ -131,8 +138,11 @@ export async function initPlatform({ export async function getRepos(): Promise<string[]> { logger.debug('Autodiscovering GitHub repositories'); try { - const res = await api.get('user/repos?per_page=100', { paginate: true }); - return res.body.map((repo: { full_name: string }) => repo.full_name); + const res = await githubApi.getJson<{ full_name: string }[]>( + 'user/repos?per_page=100', + { paginate: true } + ); + return res.body.map((repo) => repo.full_name); } catch (err) /* istanbul ignore next */ { logger.error({ err }, `GitHub getRepos error`); throw err; @@ -156,7 +166,7 @@ async function getBranchProtection( if (config.parentRepo) { return {}; } - const res = await api.get( + const res = await githubApi.getJson<BranchProtection>( `repos/${config.repository}/branches/${escapeHash(branchName)}/protection` ); return res.body; @@ -165,7 +175,7 @@ async function getBranchProtection( // Return the commit SHA for a branch async function getBranchCommit(branchName: string): Promise<string> { try { - const res = await api.get( + const res = await githubApi.getJson<{ object: { sha: string } }>( `repos/${config.repository}/git/refs/heads/${escapeHash(branchName)}` ); return res.body.object.sha; @@ -208,7 +218,7 @@ export async function initRepo({ // Necessary for Renovate Pro - do not remove logger.debug('Overriding default GitHub endpoint'); defaults.endpoint = endpoint; - api.setBaseUrl(endpoint); + githubHttp.setBaseUrl(endpoint); } const opts = hostRules.find({ hostType: PLATFORM_TYPE_GITHUB, @@ -222,7 +232,7 @@ export async function initRepo({ config.gitPrivateKey = gitPrivateKey; let res; try { - res = await api.get(`repos/${repository}`); + res = await githubApi.getJson<{ fork: boolean }>(`repos/${repository}`); logger.trace({ repositoryDetails: res.body }, 'Repository details'); config.enterpriseVersion = res.headers && (res.headers['x-github-enterprise-version'] as string); @@ -232,7 +242,7 @@ export async function initRepo({ const renovateConfig = JSON.parse( Buffer.from( ( - await api.get( + await githubApi.getJson<{ content: string }>( `repos/${config.repository}/contents/${defaultConfigFile}` ) ).body.content, @@ -265,7 +275,7 @@ export async function initRepo({ renovateConfig = JSON.parse( Buffer.from( ( - await api.get( + await githubApi.getJson<{ content: string }>( `repos/${config.repository}/contents/${defaultConfigFile}` ) ).body.content, @@ -344,16 +354,22 @@ export async function initRepo({ config.repository = null; // Get list of existing repos const existingRepos = ( - await api.get<{ full_name: string }[]>('user/repos?per_page=100', { - token: forkToken || opts.token, - paginate: true, - }) + await githubApi.getJson<{ full_name: string }[]>( + 'user/repos?per_page=100', + { + token: forkToken || opts.token, + paginate: true, + } + ) ).body.map((r) => r.full_name); try { config.repository = ( - await api.post(`repos/${repository}/forks`, { - token: forkToken || opts.token, - }) + await githubApi.postJson<{ full_name: string }>( + `repos/${repository}/forks`, + { + token: forkToken || opts.token, + } + ) ).body.full_name; } catch (err) /* istanbul ignore next */ { logger.debug({ err }, 'Error forking repository'); @@ -372,7 +388,7 @@ export async function initRepo({ // This is a lovely "hack" by GitHub that lets us force update our fork's master // with the base commit from the parent repository try { - await api.patch( + await githubApi.patchJson( `repos/${config.repository}/git/refs/heads/${config.baseBranch}`, { body: { @@ -576,7 +592,6 @@ async function getClosedPrs(): Promise<PrList> { config.closedPrList = {}; let query; try { - const url = 'graphql'; // prettier-ignore query = ` query { @@ -598,21 +613,18 @@ async function getClosedPrs(): Promise<PrList> { } } `; - const options = { - body: JSON.stringify({ query }), - json: false, - }; - const res = JSON.parse((await api.post(url, options)).body); + const nodes = await githubApi.getGraphqlNodes<any>( + query, + 'pullRequests', + { paginate: false } + ); const prNumbers: number[] = []; // istanbul ignore if - if (!res.data) { - logger.debug( - { query, res }, - 'No graphql res.data, returning empty list' - ); + if (!nodes?.length) { + logger.debug({ query }, 'No graphql data, returning empty list'); return {}; } - for (const pr of res.data.repository.pullRequests.nodes) { + for (const pr of nodes) { // https://developer.github.com/v4/object/pullrequest/ pr.displayNumber = `Pull Request #${pr.number}`; pr.state = pr.state.toLowerCase(); @@ -649,11 +661,6 @@ async function getOpenPrs(): Promise<PrList> { config.openPrList = {}; let query; try { - const url = 'graphql'; - // https://developer.github.com/v4/previews/#mergeinfopreview---more-detailed-information-about-a-pull-requests-merge-state - const headers = { - accept: 'application/vnd.github.merge-info-preview+json', - }; // prettier-ignore query = ` query { @@ -702,19 +709,18 @@ async function getOpenPrs(): Promise<PrList> { } } `; - const options = { - headers, - body: JSON.stringify({ query }), - json: false, - }; - const res = JSON.parse((await api.post(url, options)).body); + const nodes = await githubApi.getGraphqlNodes<any>( + query, + 'pullRequests', + { paginate: false } + ); const prNumbers: number[] = []; // istanbul ignore if - if (!res.data) { - logger.debug({ query, res }, 'No graphql res.data'); + if (!nodes?.length) { + logger.debug({ query }, 'No graphql res.data'); return {}; } - for (const pr of res.data.repository.pullRequests.nodes) { + for (const pr of nodes) { // https://developer.github.com/v4/object/pullrequest/ pr.displayNumber = `Pull Request #${pr.number}`; pr.state = PR_STATE_OPEN; @@ -816,12 +822,14 @@ export async function getPr(prNo: number): Promise<Pr | null> { if (!prNo) { return null; } - const openPr = (await getOpenPrs())[prNo]; + const openPrs = await getOpenPrs(); + const openPr = openPrs[prNo]; if (openPr) { logger.debug('Returning from graphql open PR list'); return openPr; } - const closedPr = (await getClosedPrs())[prNo]; + const closedPrs = await getClosedPrs(); + const closedPr = closedPrs[prNo]; if (closedPr) { logger.debug('Returning from graphql closed PR list'); return closedPr; @@ -831,7 +839,7 @@ export async function getPr(prNo: number): Promise<Pr | null> { 'PR not found in open or closed PRs list - trying to fetch it directly' ); const pr = ( - await api.get( + await githubApi.getJson<any>( `repos/${config.parentRepo || config.repository}/pulls/${prNo}` ) ).body; @@ -858,7 +866,7 @@ export async function getPr(prNo: number): Promise<Pr | null> { if (global.gitAuthor) { // Check against gitAuthor const commitAuthorEmail = ( - await api.get( + await githubApi.getJson<{ commit: { author: { email } } }[]>( `repos/${ config.parentRepo || config.repository }/pulls/${prNo}/commits` @@ -892,7 +900,9 @@ export async function getPr(prNo: number): Promise<Pr | null> { // Check if only one author of all commits logger.debug({ prNo }, 'Checking all commits'); const prCommits = ( - await api.get( + await githubApi.getJson< + { committer: { login: string }; commit: { message: string } }[] + >( `repos/${ config.parentRepo || config.repository }/pulls/${prNo}/commits` @@ -900,10 +910,7 @@ export async function getPr(prNo: number): Promise<Pr | null> { ).body; // Filter out "Update branch" presses const remainingCommits = prCommits.filter( - (commit: { - committer: { login: string }; - commit: { message: string }; - }) => { + (commit: { committer; commit }) => { const isWebflow = commit.committer && commit.committer.login === 'web-flow'; if (!isWebflow) { @@ -950,7 +957,15 @@ export async function getPrList(): Promise<Pr[]> { logger.debug('Retrieving PR list'); let res; try { - res = await api.get( + res = await githubApi.getJson<{ + number: number; + head: { ref: string; sha: string; repo: { full_name: string } }; + title: string; + state: string; + merged_at: string; + created_at: string; + closed_at: string; + }>( `repos/${ config.parentRepo || config.repository }/pulls?per_page=100&state=all`, @@ -960,30 +975,19 @@ export async function getPrList(): Promise<Pr[]> { logger.debug({ err }, 'getPrList err'); throw new Error('platform-failure'); } - config.prList = res.body.map( - (pr: { - number: number; - head: { ref: string; sha: string; repo: { full_name: string } }; - title: string; - state: string; - merged_at: string; - created_at: string; - closed_at: string; - }) => ({ - number: pr.number, - branchName: pr.head.ref, - sha: pr.head.sha, - title: pr.title, - state: - pr.state === PR_STATE_CLOSED && pr.merged_at && pr.merged_at.length - ? /* istanbul ignore next */ 'merged' - : pr.state, - createdAt: pr.created_at, - closed_at: pr.closed_at, - sourceRepo: - pr.head && pr.head.repo ? pr.head.repo.full_name : undefined, - }) - ); + config.prList = res.body.map((pr) => ({ + number: pr.number, + branchName: pr.head.ref, + sha: pr.head.sha, + title: pr.title, + state: + pr.state === PR_STATE_CLOSED && pr.merged_at && pr.merged_at.length + ? /* istanbul ignore next */ 'merged' + : pr.state, + createdAt: pr.created_at, + closed_at: pr.closed_at, + sourceRepo: pr.head && pr.head.repo ? pr.head.repo.full_name : undefined, + })); logger.debug(`Retrieved ${config.prList.length} Pull Requests`); } return config.prList; @@ -1027,7 +1031,9 @@ async function getStatus( branchName )}/status`; - return (await api.get(commitStatusUrl, { useCache })).body; + return ( + await githubApi.getJson<CombinedBranchStatus>(commitStatusUrl, { useCache }) + ).body; } // Returns the combined status for a branch. @@ -1074,15 +1080,17 @@ export async function getBranchStatus( Accept: 'application/vnd.github.antiope-preview+json', }, }; - const checkRunsRaw = (await api.get(checkRunsUrl, opts)).body; + const checkRunsRaw = ( + await githubApi.getJson<{ + check_runs: { name: string; status: string; conclusion: string }[]; + }>(checkRunsUrl, opts) + ).body; if (checkRunsRaw.check_runs && checkRunsRaw.check_runs.length) { - checkRuns = checkRunsRaw.check_runs.map( - (run: { name: string; status: string; conclusion: string }) => ({ - name: run.name, - status: run.status, - conclusion: run.conclusion, - }) - ); + checkRuns = checkRunsRaw.check_runs.map((run) => ({ + name: run.name, + status: run.status, + conclusion: run.conclusion, + })); logger.debug({ checkRuns }, 'check runs result'); } else { // istanbul ignore next @@ -1136,7 +1144,7 @@ async function getStatusCheck( const url = `repos/${config.repository}/commits/${branchCommit}/statuses`; - return (await api.get(url, { useCache })).body; + return (await githubApi.getJson<GhBranchStatus[]>(url, { useCache })).body; } const githubToRenovateStatusMapping = { @@ -1202,7 +1210,7 @@ export async function setBranchStatus({ if (targetUrl) { options.target_url = targetUrl; } - await api.post(url, { body: options }); + await githubApi.postJson(url, { body: options }); // update status cache await getStatus(branchName, false); @@ -1237,7 +1245,7 @@ async function getIssues(): Promise<Issue[]> { } `; - const result = await getGraphqlNodes<Issue>(query, 'issues'); + const result = await githubApi.getGraphqlNodes<Issue>(query, 'issues'); logger.debug(`Retrieved ${result.length} issues`); return result.map((issue) => ({ @@ -1264,7 +1272,7 @@ export async function findIssue(title: string): Promise<Issue | null> { } logger.debug('Found issue ' + issue.number); const issueBody = ( - await api.get( + await githubApi.getJson<{ body: string }>( `repos/${config.parentRepo || config.repository}/issues/${issue.number}` ) ).body.body; @@ -1276,7 +1284,7 @@ export async function findIssue(title: string): Promise<Issue | null> { async function closeIssue(issueNumber: number): Promise<void> { logger.debug(`closeIssue(${issueNumber})`); - await api.patch( + await githubApi.patchJson( `repos/${config.parentRepo || config.repository}/issues/${issueNumber}`, { body: { state: 'closed' }, @@ -1314,7 +1322,7 @@ export async function ensureIssue({ } } const issueBody = ( - await api.get( + await githubApi.getJson<{ body: string }>( `repos/${config.parentRepo || config.repository}/issues/${ issue.number }` @@ -1326,7 +1334,7 @@ export async function ensureIssue({ } if (shouldReOpen) { logger.debug('Patching issue'); - await api.patch( + await githubApi.patchJson( `repos/${config.parentRepo || config.repository}/issues/${ issue.number }`, @@ -1338,12 +1346,15 @@ export async function ensureIssue({ return 'updated'; } } - await api.post(`repos/${config.parentRepo || config.repository}/issues`, { - body: { - title, - body, - }, - }); + await githubApi.postJson( + `repos/${config.parentRepo || config.repository}/issues`, + { + body: { + title, + body, + }, + } + ); logger.info('Issue created'); // reset issueList so that it will be fetched again as-needed delete config.issueList; @@ -1381,7 +1392,7 @@ export async function addAssignees( ): Promise<void> { logger.debug(`Adding assignees ${assignees} to #${issueNo}`); const repository = config.parentRepo || config.repository; - await api.post(`repos/${repository}/issues/${issueNo}/assignees`, { + await githubApi.postJson(`repos/${repository}/issues/${issueNo}/assignees`, { body: { assignees, }, @@ -1399,7 +1410,7 @@ export async function addReviewers( .filter((e) => e.startsWith('team:')) .map((e) => e.replace(/^team:/, '')); try { - await api.post( + await githubApi.postJson( `repos/${ config.parentRepo || config.repository }/pulls/${prNo}/requested_reviewers`, @@ -1422,7 +1433,7 @@ async function addLabels( logger.debug(`Adding labels ${labels} to #${issueNo}`); const repository = config.parentRepo || config.repository; if (is.array(labels) && labels.length) { - await api.post(`repos/${repository}/issues/${issueNo}/labels`, { + await githubApi.postJson(`repos/${repository}/issues/${issueNo}/labels`, { body: labels, }); } @@ -1435,7 +1446,9 @@ export async function deleteLabel( logger.debug(`Deleting label ${label} from #${issueNo}`); const repository = config.parentRepo || config.repository; try { - await api.delete(`repos/${repository}/issues/${issueNo}/labels/${label}`); + await githubApi.deleteJson( + `repos/${repository}/issues/${issueNo}/labels/${label}` + ); } catch (err) /* istanbul ignore next */ { logger.warn({ err, issueNo, label }, 'Failed to delete label'); } @@ -1443,7 +1456,7 @@ export async function deleteLabel( async function addComment(issueNo: number, body: string): Promise<void> { // POST /repos/:owner/:repo/issues/:number/comments - await api.post( + await githubApi.postJson( `repos/${ config.parentRepo || config.repository }/issues/${issueNo}/comments`, @@ -1455,7 +1468,7 @@ async function addComment(issueNo: number, body: string): Promise<void> { async function editComment(commentId: number, body: string): Promise<void> { // PATCH /repos/:owner/:repo/issues/comments/:id - await api.patch( + await githubApi.patchJson( `repos/${ config.parentRepo || config.repository }/issues/comments/${commentId}`, @@ -1467,7 +1480,7 @@ async function editComment(commentId: number, body: string): Promise<void> { async function deleteComment(commentId: number): Promise<void> { // DELETE /repos/:owner/:repo/issues/comments/:id - await api.delete( + await githubApi.deleteJson( `repos/${ config.parentRepo || config.repository }/issues/comments/${commentId}` @@ -1487,7 +1500,7 @@ async function getComments(issueNo: number): Promise<Comment[]> { }/issues/${issueNo}/comments?per_page=100`; try { const comments = ( - await api.get<Comment[]>(url, { + await githubApi.getJson<Comment[]>(url, { paginate: true, }) ).body; @@ -1622,7 +1635,7 @@ export async function createPr({ } logger.debug({ title, head, base }, 'Creating PR'); const pr = ( - await api.post<GhPr>( + await githubApi.postJson<GhPr>( `repos/${config.parentRepo || config.repository}/pulls`, options ) @@ -1656,11 +1669,11 @@ export async function getPrFiles(prNo: number): Promise<string[]> { return []; } const files = ( - await api.get( + await githubApi.getJson<{ filename: string }[]>( `repos/${config.parentRepo || config.repository}/pulls/${prNo}/files` ) ).body; - return files.map((f: { filename: string }) => f.filename); + return files.map((f) => f.filename); } export async function updatePr( @@ -1682,7 +1695,7 @@ export async function updatePr( options.token = config.forkToken; } try { - await api.patch( + await githubApi.patchJson( `repos/${config.parentRepo || config.repository}/pulls/${prNo}`, options ); @@ -1715,9 +1728,11 @@ export async function mergePr( 'Branch protection: Attempting to merge PR when PR reviews are enabled' ); const repository = config.parentRepo || config.repository; - const reviews = await api.get(`repos/${repository}/pulls/${prNo}/reviews`); + const reviews = await githubApi.getJson<{ state: string }[]>( + `repos/${repository}/pulls/${prNo}/reviews` + ); const isApproved = reviews.body.some( - (review: { state: string }) => review.state === 'APPROVED' + (review) => review.state === 'APPROVED' ); if (!isApproved) { logger.debug( @@ -1740,7 +1755,7 @@ export async function mergePr( options.body.merge_method = config.mergeMethod; try { logger.debug({ options, url }, `mergePr`); - await api.put(url, options); + await githubApi.putJson(url, options); automerged = true; } catch (err) { if (err.statusCode === 404 || err.statusCode === 405) { @@ -1760,7 +1775,7 @@ export async function mergePr( options.body.merge_method = 'rebase'; try { logger.debug({ options, url }, `mergePr`); - await api.put(url, options); + await githubApi.putJson(url, options); } catch (err1) { logger.debug( { err: err1 }, @@ -1769,7 +1784,7 @@ export async function mergePr( try { options.body.merge_method = 'squash'; logger.debug({ options, url }, `mergePr`); - await api.put(url, options); + await githubApi.putJson(url, options); } catch (err2) { logger.debug( { err: err2 }, @@ -1778,7 +1793,7 @@ export async function mergePr( try { options.body.merge_method = 'merge'; logger.debug({ options, url }, `mergePr`); - await api.put(url, options); + await githubApi.putJson(url, options); } catch (err3) { logger.debug( { err: err3 }, @@ -1811,10 +1826,6 @@ export function getPrBody(input: string): string { } export async function getVulnerabilityAlerts(): Promise<VulnerabilityAlert[]> { - const headers = { - accept: 'application/vnd.github.vixen-preview+json', - }; - const url = 'graphql'; // prettier-ignore const query = ` query { @@ -1842,18 +1853,18 @@ export async function getVulnerabilityAlerts(): Promise<VulnerabilityAlert[]> { } } }`; - const options = { - headers, - body: JSON.stringify({ query }), - json: false, - }; let alerts = []; try { - const res = JSON.parse((await api.post(url, options)).body); - if (res?.data?.repository?.vulnerabilityAlerts) { - alerts = res.data.repository.vulnerabilityAlerts.edges.map( - (edge: { node: any }) => edge.node - ); + const vulnerabilityAlerts = await githubApi.getGraphqlNodes<{ node: any }>( + query, + 'vulnerabilityAlerts', + { + paginate: false, + acceptHeader: 'application/vnd.github.vixen-preview+json', + } + ); + if (vulnerabilityAlerts?.length) { + alerts = vulnerabilityAlerts.map((edge) => edge.node); if (alerts.length) { logger.debug({ alerts }, 'Found GitHub vulnerability alerts'); } diff --git a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap index 426d781813..568032b650 100644 --- a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap +++ b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap @@ -51,6 +51,110 @@ Object { } `; +exports[`workers/pr/changelog getChangeLogJSON filters unnecessary warns 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/tags?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/contents/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/contents/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/contents/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/contents/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100", + }, +] +`; + exports[`workers/pr/changelog getChangeLogJSON supports github enterprise and github enterprise changelog 1`] = ` Object { "hasReleaseNotes": true, @@ -102,6 +206,44 @@ Object { } `; +exports[`workers/pr/changelog getChangeLogJSON supports github enterprise and github enterprise changelog 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "github-enterprise.example.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://github-enterprise.example.com/repos/chalk/chalk/tags?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "github-enterprise.example.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://github-enterprise.example.com/repos/chalk/chalk/contents/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "github-enterprise.example.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://github-enterprise.example.com/repos/chalk/chalk/releases?per_page=100", + }, +] +`; + exports[`workers/pr/changelog getChangeLogJSON supports github enterprise and github.com changelog 1`] = ` Object { "hasReleaseNotes": true, @@ -153,6 +295,44 @@ Object { } `; +exports[`workers/pr/changelog getChangeLogJSON supports github enterprise and github.com changelog 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/tags?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/contents/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100", + }, +] +`; + exports[`workers/pr/changelog getChangeLogJSON supports github.com and github enterprise changelog 1`] = ` Object { "hasReleaseNotes": true, @@ -204,6 +384,44 @@ Object { } `; +exports[`workers/pr/changelog getChangeLogJSON supports github.com and github enterprise changelog 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "github-enterprise.example.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://github-enterprise.example.com/repos/chalk/chalk/tags?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "github-enterprise.example.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://github-enterprise.example.com/repos/chalk/chalk/contents/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "github-enterprise.example.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://github-enterprise.example.com/repos/chalk/chalk/releases?per_page=100", + }, +] +`; + exports[`workers/pr/changelog getChangeLogJSON supports node engines 1`] = ` Object { "hasReleaseNotes": true, @@ -310,6 +528,44 @@ Object { } `; +exports[`workers/pr/changelog getChangeLogJSON uses GitHub tags 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/tags?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/contents/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "token abc", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100", + }, +] +`; + exports[`workers/pr/changelog getChangeLogJSON works without Github 1`] = ` Object { "hasReleaseNotes": true, diff --git a/lib/workers/pr/changelog/index.spec.ts b/lib/workers/pr/changelog/index.spec.ts index 7ad98cb885..6a06a73c2e 100644 --- a/lib/workers/pr/changelog/index.spec.ts +++ b/lib/workers/pr/changelog/index.spec.ts @@ -1,17 +1,17 @@ -import { mocked, partial } from '../../../../test/util'; +import * as httpMock from '../../../../test/httpMock'; +import { partial } from '../../../../test/util'; import { PLATFORM_TYPE_GITHUB } from '../../../constants/platforms'; -import { api } from '../../../platform/github/gh-got-wrapper'; import * as globalCache from '../../../util/cache/global'; +import { clear } from '../../../util/cache/run'; import * as runCache from '../../../util/cache/run'; import * as hostRules from '../../../util/host-rules'; import * as semverVersioning from '../../../versioning/semver'; import { BranchConfig } from '../../common'; import { ChangeLogError, getChangeLogJSON } from '.'; -jest.mock('../../../platform/github/gh-got-wrapper'); jest.mock('../../../datasource/npm'); -const ghGot = mocked(api).get; +const githubApiHost = 'https://api.github.com'; const upgrade: BranchConfig = partial<BranchConfig>({ endpoint: 'https://api.github.com/', @@ -37,7 +37,7 @@ const upgrade: BranchConfig = partial<BranchConfig>({ describe('workers/pr/changelog', () => { describe('getChangeLogJSON', () => { beforeEach(async () => { - ghGot.mockClear(); + httpMock.setup(); hostRules.clear(); hostRules.add({ hostType: PLATFORM_TYPE_GITHUB, @@ -47,25 +47,34 @@ describe('workers/pr/changelog', () => { await globalCache.rmAll(); runCache.clear(); }); + + afterEach(() => { + clear(); + httpMock.reset(); + }); + it('returns null if @types', async () => { + httpMock.scope(githubApiHost); expect( await getChangeLogJSON({ ...upgrade, fromVersion: null, }) ).toBeNull(); - expect(ghGot).toHaveBeenCalledTimes(0); + expect(httpMock.getTrace()).toHaveLength(0); }); it('returns null if no fromVersion', async () => { + httpMock.scope(githubApiHost); expect( await getChangeLogJSON({ ...upgrade, sourceUrl: 'https://github.com/DefinitelyTyped/DefinitelyTyped', }) ).toBeNull(); - expect(ghGot).toHaveBeenCalledTimes(0); + expect(httpMock.getTrace()).toHaveLength(0); }); it('returns null if fromVersion equals toVersion', async () => { + httpMock.scope(githubApiHost); expect( await getChangeLogJSON({ ...upgrade, @@ -73,50 +82,61 @@ describe('workers/pr/changelog', () => { toVersion: '1.0.0', }) ).toBeNull(); - expect(ghGot).toHaveBeenCalledTimes(0); + expect(httpMock.getTrace()).toHaveLength(0); }); it('skips invalid repos', async () => { + httpMock.scope(githubApiHost); expect( await getChangeLogJSON({ ...upgrade, sourceUrl: 'https://github.com/about', }) ).toBeNull(); + expect(httpMock.getTrace()).toHaveLength(0); }); it('works without Github', async () => { + httpMock.scope(githubApiHost); expect( await getChangeLogJSON({ ...upgrade, }) ).toMatchSnapshot(); + expect(httpMock.getTrace()).toHaveLength(0); }); it('uses GitHub tags', async () => { - ghGot.mockResolvedValueOnce({ - body: [ + httpMock + .scope(githubApiHost) + .get('/repos/chalk/chalk/tags?per_page=100') + .reply(200, [ { name: '0.9.0' }, { name: '1.0.0' }, { name: '1.4.0' }, { name: 'v2.3.0' }, { name: '2.2.2' }, { name: 'v2.4.2' }, - ], - } as never); + ]) + .persist() + .get(/.*/) + .reply(200, []); expect( await getChangeLogJSON({ ...upgrade, }) ).toMatchSnapshot(); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('filters unnecessary warns', async () => { - ghGot.mockImplementation(() => { - throw new Error('Unknown Github Repo'); + httpMock + .scope(githubApiHost) + .persist() + .get(/.*/) + .replyWithError('Unknown Github Repo'); + const res = await getChangeLogJSON({ + ...upgrade, + depName: '@renovate/no', }); - expect( - await getChangeLogJSON({ - ...upgrade, - depName: '@renovate/no', - }) - ).toMatchSnapshot(); + expect(res).toMatchSnapshot(); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('supports node engines', async () => { expect( @@ -167,6 +187,7 @@ describe('workers/pr/changelog', () => { ).toBeNull(); }); it('supports github enterprise and github.com changelog', async () => { + httpMock.scope(githubApiHost).persist().get(/.*/).reply(200, []); hostRules.add({ hostType: PLATFORM_TYPE_GITHUB, token: 'super_secret', @@ -178,21 +199,14 @@ describe('workers/pr/changelog', () => { endpoint: 'https://github-enterprise.example.com/', }) ).toMatchSnapshot(); - expect(ghGot).toHaveBeenNthCalledWith( - 1, - 'https://api.github.com/repos/chalk/chalk/tags?per_page=100', - { paginate: true } - ); - expect(ghGot).toHaveBeenNthCalledWith( - 2, - 'https://api.github.com/repos/chalk/chalk/contents/' - ); - expect(ghGot).toHaveBeenNthCalledWith( - 3, - 'https://api.github.com/repos/chalk/chalk/releases?per_page=100' - ); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('supports github enterprise and github enterprise changelog', async () => { + httpMock + .scope('https://github-enterprise.example.com') + .persist() + .get(/.*/) + .reply(200, []); hostRules.add({ hostType: PLATFORM_TYPE_GITHUB, baseUrl: 'https://github-enterprise.example.com/', @@ -206,22 +220,15 @@ describe('workers/pr/changelog', () => { endpoint: 'https://github-enterprise.example.com/', }) ).toMatchSnapshot(); - expect(ghGot).toHaveBeenNthCalledWith( - 1, - 'https://github-enterprise.example.com/repos/chalk/chalk/tags?per_page=100', - { paginate: true } - ); - expect(ghGot).toHaveBeenNthCalledWith( - 2, - 'https://github-enterprise.example.com/repos/chalk/chalk/contents/' - ); - expect(ghGot).toHaveBeenNthCalledWith( - 3, - 'https://github-enterprise.example.com/repos/chalk/chalk/releases?per_page=100' - ); + expect(httpMock.getTrace()).toMatchSnapshot(); }); it('supports github.com and github enterprise changelog', async () => { + httpMock + .scope('https://github-enterprise.example.com') + .persist() + .get(/.*/) + .reply(200, []); hostRules.add({ hostType: PLATFORM_TYPE_GITHUB, baseUrl: 'https://github-enterprise.example.com/', @@ -233,19 +240,7 @@ describe('workers/pr/changelog', () => { sourceUrl: 'https://github-enterprise.example.com/chalk/chalk', }) ).toMatchSnapshot(); - expect(ghGot).toHaveBeenNthCalledWith( - 1, - 'https://github-enterprise.example.com/repos/chalk/chalk/tags?per_page=100', - { paginate: true } - ); - expect(ghGot).toHaveBeenNthCalledWith( - 2, - 'https://github-enterprise.example.com/repos/chalk/chalk/contents/' - ); - expect(ghGot).toHaveBeenNthCalledWith( - 3, - 'https://github-enterprise.example.com/repos/chalk/chalk/releases?per_page=100' - ); + expect(httpMock.getTrace()).toMatchSnapshot(); }); }); }); diff --git a/lib/workers/pr/changelog/release-notes.ts b/lib/workers/pr/changelog/release-notes.ts index dd2da999f1..c42a15063d 100644 --- a/lib/workers/pr/changelog/release-notes.ts +++ b/lib/workers/pr/changelog/release-notes.ts @@ -4,15 +4,15 @@ import { linkify } from 'linkify-markdown'; import MarkdownIt from 'markdown-it'; import { logger } from '../../../logger'; -import { api } from '../../../platform/github/gh-got-wrapper'; import * as globalCache from '../../../util/cache/global'; +import { GithubHttp } from '../../../util/http/github'; import { ChangeLogNotes, ChangeLogResult } from './common'; -const { get: ghGot } = api; - const markdown = new MarkdownIt('zero'); markdown.enable(['heading', 'lheading']); +const http = new GithubHttp(); + export async function getReleaseList( apiBaseUrl: string, repository: string @@ -25,7 +25,7 @@ export async function getReleaseList( try { let url = apiBaseUrl.replace(/\/?$/, '/'); url += `repos/${repository}/releases?per_page=100`; - const res = await ghGot< + const res = await http.getJson< { html_url: string; id: number; @@ -161,7 +161,7 @@ export async function getReleaseNotesMd( let apiPrefix = apiBaseUrl.replace(/\/?$/, '/'); apiPrefix += `repos/${repository}/contents/`; - const filesRes = await ghGot<{ name: string }[]>(apiPrefix); + const filesRes = await http.getJson<{ name: string }[]>(apiPrefix); const files = filesRes.body .map((f) => f.name) .filter((f) => changelogFilenameRegex.test(f)); @@ -176,7 +176,7 @@ export async function getReleaseNotesMd( `Multiple candidates for changelog file, using ${changelogFile}` ); } - const fileRes = await ghGot<{ content: string }>( + const fileRes = await http.getJson<{ content: string }>( `${apiPrefix}/${changelogFile}` ); changelogMd = diff --git a/lib/workers/pr/changelog/source-github.ts b/lib/workers/pr/changelog/source-github.ts index 70d8c0efa4..629a55cb17 100644 --- a/lib/workers/pr/changelog/source-github.ts +++ b/lib/workers/pr/changelog/source-github.ts @@ -2,15 +2,15 @@ import URL from 'url'; import { PLATFORM_TYPE_GITHUB } from '../../../constants/platforms'; import { Release } from '../../../datasource'; import { logger } from '../../../logger'; -import { api } from '../../../platform/github/gh-got-wrapper'; import * as globalCache from '../../../util/cache/global'; import * as hostRules from '../../../util/host-rules'; +import { GithubHttp } from '../../../util/http/github'; import * as allVersioning from '../../../versioning'; import { BranchUpgradeConfig } from '../../common'; import { ChangeLogError, ChangeLogRelease, ChangeLogResult } from './common'; import { addReleaseNotes } from './release-notes'; -const { get: ghGot } = api; +const http = new GithubHttp(); async function getTags( endpoint: string, @@ -18,7 +18,7 @@ async function getTags( ): Promise<string[]> { const url = `${endpoint}repos/${repository}/tags?per_page=100`; try { - const res = await ghGot<{ name: string }[]>(url, { + const res = await http.getJson<{ name: string }[]>(url, { paginate: true, }); -- GitLab