diff --git a/lib/platform/github/gh-got-wrapper.js b/lib/platform/github/gh-got-wrapper.js index 75e0a4b3abc80fe5582de843aa1e6a1a11060115..f799502e648b888683e07637e6286768b5c6d944 100644 --- a/lib/platform/github/gh-got-wrapper.js +++ b/lib/platform/github/gh-got-wrapper.js @@ -1,35 +1,28 @@ const URL = require('url'); -const ghGot = require('gh-got'); const delay = require('delay'); const parseLinkHeader = require('parse-link-header'); const pAll = require('p-all'); -const hostRules = require('../../util/host-rules'); +const got = require('../../util/got'); const { maskToken } = require('../../util/mask'); -let cache = {}; let stats = {}; -let endpoint = 'https://api.github.com/'; +const hostType = 'github'; +let baseUrl = 'https://api.github.com/'; async function get(path, options, retries = 5) { - let url = URL.resolve(endpoint, path); const opts = { - // TODO: Move to configurable host rules, or use utils/got - timeout: 60 * 1000, - ...hostRules.find({ hostType: 'github', url }), + hostType, + baseUrl, + json: true, ...options, }; - delete opts.endpoint; const method = opts.method || 'get'; - const useCache = opts.useCache !== false; + let massagedPath = path; if (method.toLowerCase() === 'post' && path === 'graphql') { // GitHub Enterprise uses unversioned graphql path - url = url.replace('/v3/', '/'); - } - if (method === 'get' && useCache && cache[path]) { - logger.trace({ path }, 'Returning cached result'); - return cache[path]; + massagedPath = massagedPath.replace('/v3/', '/'); } logger.trace(`${method.toUpperCase()} ${path}`); stats.requests = (stats.requests || []).concat([ @@ -52,7 +45,7 @@ async function get(path, options, retries = 5) { opts.headers.accept = `${appAccept}, ${opts.headers.accept}`; } } - const res = await ghGot(url, opts); + const res = await got(massagedPath, opts); if (res && res.headers) { stats.rateLimit = res.headers['x-ratelimit-limit']; stats.rateLimitRemaining = res.headers['x-ratelimit-remaining']; @@ -86,13 +79,6 @@ async function get(path, options, retries = 5) { ); } } - if ( - method === 'get' && - (path.startsWith('repos/') || - path.startsWith('https://api.github.com/repos/')) - ) { - cache[path] = res; - } // istanbul ignore if if (method === 'POST' && path === 'graphql') { const goodResult = '{"data":{'; @@ -171,12 +157,6 @@ async function get(path, options, retries = 5) { ) { logger.info({ err, headers: err.headers }, 'Rate limit exceeded'); throw new Error('rate-limit-exceeded'); - } else if ( - err.statusCode === 403 && - err.message && - err.message.includes('blobs up to 1 MB in size') - ) { - throw err; } else if ( err.statusCode === 403 && err.message && @@ -229,8 +209,6 @@ get.setAppMode = function setAppMode() { }; get.reset = function reset() { - cache = null; - cache = {}; // istanbul ignore if if (stats.requests && stats.requests.length > 1) { logger.info( @@ -247,8 +225,8 @@ get.reset = function reset() { } }; -get.setEndpoint = e => { - endpoint = e; +get.setBaseUrl = u => { + baseUrl = u; }; module.exports = get; diff --git a/lib/platform/github/index.js b/lib/platform/github/index.js index 0985de6ef53e787fb2d846b0166a802d259da559..98d0fc2f1f1b5050c675c3f4afa5b9331f888e2b 100644 --- a/lib/platform/github/index.js +++ b/lib/platform/github/index.js @@ -81,7 +81,7 @@ async function initPlatform({ endpoint, token }) { const res = {}; if (endpoint) { defaults.endpoint = endpoint.replace(/\/?$/, '/'); // always add a trailing slash - get.setEndpoint(defaults.endpoint); + get.setBaseUrl(defaults.endpoint); } else { logger.info('Using default github endpoint: ' + defaults.endpoint); } diff --git a/test/platform/github/__snapshots__/gh-got-wrapper.spec.js.snap b/test/platform/github/__snapshots__/gh-got-wrapper.spec.js.snap deleted file mode 100644 index 0751c2e7956ec95a1a20e01624f76c86b9111649..0000000000000000000000000000000000000000 --- a/test/platform/github/__snapshots__/gh-got-wrapper.spec.js.snap +++ /dev/null @@ -1,8 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`platform/gh-got-wrapper should throw for blob size 1`] = ` -Object { - "message": "This API returns blobs up to 1 MB in size. The requested blob is too large to fetch via the API, but you can use the Git Data API to request blobs up to 100 MB in size. (403)", - "statusCode": 403, -} -`; diff --git a/test/platform/github/gh-got-wrapper.spec.js b/test/platform/github/gh-got-wrapper.spec.js index 85bf26650cd17a008f6c08c2bf98ae379803c451..9bbd2117f7045c8aa9a84be6521e9ae7b090f776 100644 --- a/test/platform/github/gh-got-wrapper.spec.js +++ b/test/platform/github/gh-got-wrapper.spec.js @@ -1,8 +1,8 @@ -const ghGot = require('gh-got'); const delay = require('delay'); +const got = require('../../../lib/util/got'); const get = require('../../../lib/platform/github/gh-got-wrapper'); -jest.mock('gh-got'); +jest.mock('../../../lib/util/got'); jest.mock('delay'); describe('platform/gh-got-wrapper', () => { @@ -16,61 +16,61 @@ describe('platform/gh-got-wrapper', () => { it('supports app mode', async () => { global.appMode = true; await get('some-url', { headers: { accept: 'some-accept' } }); - expect(ghGot.mock.calls[0][1].headers.accept).toBe( + expect(got.mock.calls[0][1].headers.accept).toBe( 'application/vnd.github.machine-man-preview+json, some-accept' ); }); it('strips v3 for graphql', async () => { - ghGot.mockImplementationOnce(() => ({ + got.mockImplementationOnce(() => ({ body: '{"data":{', })); - get.setEndpoint('https://ghe.mycompany.com/api/v3/'); + get.setBaseUrl('https://ghe.mycompany.com/api/v3/'); await get.post('graphql', { body: 'abc', }); - expect(ghGot.mock.calls[0][0].includes('/v3')).toBe(false); + expect(got.mock.calls[0][0].includes('/v3')).toBe(false); }); it('paginates', async () => { - ghGot.mockReturnValueOnce({ + 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'], }); - ghGot.mockReturnValueOnce({ + 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'], }); - ghGot.mockReturnValueOnce({ + got.mockReturnValueOnce({ headers: {}, body: ['d'], }); const res = await get('some-url', { paginate: true }); expect(res.body).toEqual(['a', 'b', 'c', 'd']); - expect(ghGot).toHaveBeenCalledTimes(3); + expect(got).toHaveBeenCalledTimes(3); }); it('attempts to paginate', async () => { - ghGot.mockReturnValueOnce({ + got.mockReturnValueOnce({ headers: { link: '<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last"', }, body: ['a'], }); - ghGot.mockReturnValueOnce({ + got.mockReturnValueOnce({ headers: {}, body: ['b'], }); const res = await get('some-url', { paginate: true }); expect(res.body).toHaveLength(1); - expect(ghGot).toHaveBeenCalledTimes(1); + expect(got).toHaveBeenCalledTimes(1); }); it('should throw rate limit exceeded', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 403, message: @@ -80,7 +80,7 @@ describe('platform/gh-got-wrapper', () => { await expect(get('some-url')).rejects.toThrow(); }); it('should throw Bad credentials', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 401, message: 'Bad credentials. (401)', @@ -96,7 +96,7 @@ describe('platform/gh-got-wrapper', () => { expect(e.message).toEqual('bad-credentials'); }); it('should throw platform failure', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 401, message: 'Bad credentials. (401)', @@ -115,7 +115,7 @@ describe('platform/gh-got-wrapper', () => { expect(e.message).toEqual('platform-failure'); }); it('should throw platform failure ENOTFOUND', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ name: 'RequestError', code: 'ENOTFOUND', @@ -131,7 +131,7 @@ describe('platform/gh-got-wrapper', () => { expect(e.message).toEqual('platform-failure'); }); it('should throw platform failure for 500', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 500, message: 'Internal Server Error', @@ -146,78 +146,61 @@ describe('platform/gh-got-wrapper', () => { expect(e).toBeDefined(); expect(e.message).toEqual('platform-failure'); }); - it('should throw for blob size', async () => { - ghGot.mockImplementationOnce(() => - Promise.reject({ - statusCode: 403, - message: - 'This API returns blobs up to 1 MB in size. The requested blob is too large to fetch via the API, but you can use the Git Data API to request blobs up to 100 MB in size. (403)', - }) - ); - let e; - try { - await get('some-url'); - } catch (err) { - e = err; - } - expect(e).toBeDefined(); - expect(e).toMatchSnapshot(); - }); it('should retry 502s', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 502, }) ); - ghGot.mockImplementationOnce(() => ({ + got.mockImplementationOnce(() => ({ body, })); const res = await get('some-url'); expect(res.body).toEqual(body); }); it('should retry 502s until success', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 502, }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 502, }) ); - ghGot.mockImplementationOnce(() => ({ + got.mockImplementationOnce(() => ({ body, })); const res = await get('some-url'); - expect(ghGot).toHaveBeenCalledTimes(3); + expect(got).toHaveBeenCalledTimes(3); expect(res.body).toEqual(body); }); it('should retry until failure', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 502, }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 403, message: 'Bad bot.', }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 502, }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 403, message: 'You have triggered an abuse detection mechanism. Please wait a few minutes before you try again.', }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 404, }) @@ -227,32 +210,32 @@ describe('platform/gh-got-wrapper', () => { }); }); it('should give up after 5 retries', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 502, }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 502, }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 500, }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 500, }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 502, }) ); - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 403, message: 'Bad bot.', @@ -264,28 +247,19 @@ describe('platform/gh-got-wrapper', () => { }); }); it('should retry posts', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 502, }) ); - ghGot.mockImplementationOnce(() => ({ + got.mockImplementationOnce(() => ({ body, })); const res = await get.post('some-url'); expect(res.body).toEqual(body); }); - it('returns cached', async () => { - get.reset(); - ghGot.mockReturnValueOnce({ - body: {}, - }); - const res1 = await get('repos/foo'); - const res2 = await get('repos/foo'); - expect(res1).toEqual(res2); - }); it('should throw platform failure ParseError', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ name: 'ParseError', }) @@ -300,7 +274,7 @@ describe('platform/gh-got-wrapper', () => { expect(e.message).toEqual('platform-failure'); }); it('should throw for unauthorized integration', async () => { - ghGot.mockImplementationOnce(() => + got.mockImplementationOnce(() => Promise.reject({ statusCode: 403, message: 'Resource not accessible by integration (403)', diff --git a/test/platform/github/index.spec.js b/test/platform/github/index.spec.js index e8ddb0eca598032ebdb72ac93efb3ca330c05858..e1c6aadae60e3ccc46992e709e2ccd6c3571ae7a 100644 --- a/test/platform/github/index.spec.js +++ b/test/platform/github/index.spec.js @@ -11,6 +11,7 @@ describe('platform/github', () => { jest.mock('delay'); jest.mock('../../../lib/platform/github/gh-got-wrapper'); jest.mock('../../../lib/util/host-rules'); + jest.mock('../../../lib/util/got'); get = require('../../../lib/platform/github/gh-got-wrapper'); github = require('../../../lib/platform/github'); hostRules = require('../../../lib/util/host-rules'); diff --git a/test/workers/pr/changelog/index.spec.js b/test/workers/pr/changelog/index.spec.js index b94ecbed7abb76e838c75cbf95a70092435f5ea5..5dbca1c35bb17060277bd60b8279180e03d4081c 100644 --- a/test/workers/pr/changelog/index.spec.js +++ b/test/workers/pr/changelog/index.spec.js @@ -1,6 +1,6 @@ jest.mock('../../../../lib/platform/github/gh-got-wrapper'); jest.mock('../../../../lib/datasource/npm'); -jest.mock('got'); +jest.mock('../../../../lib/platform/github/gh-got-wrapper'); const hostRules = require('../../../../lib/util/host-rules'); const ghGot = require('../../../../lib/platform/github/gh-got-wrapper'); diff --git a/test/workers/pr/changelog/release-notes.spec.js b/test/workers/pr/changelog/release-notes.spec.js index d8d1aa6a991e426aebbf7c5de11c7ae4d184e731..954b9735ea61c30ef6841c968f19c5edef02a12f 100644 --- a/test/workers/pr/changelog/release-notes.spec.js +++ b/test/workers/pr/changelog/release-notes.spec.js @@ -1,5 +1,5 @@ const fs = require('fs-extra'); -const ghGot = require('gh-got'); +const ghGot = require('../../../../lib/util/got'); const { addReleaseNotes, getReleaseNotes, @@ -26,7 +26,7 @@ const contentsResponse = [ { name: 'README.md' }, ]; -jest.mock('gh-got'); +jest.mock('../../../../lib/util/got'); describe('workers/pr/release-notes', () => { describe('addReleaseNotes()', () => {