diff --git a/lib/datasource/hex/index.js b/lib/datasource/hex/index.js new file mode 100644 index 0000000000000000000000000000000000000000..4b14939f58899c72cbfae100056bde479d99dc9f --- /dev/null +++ b/lib/datasource/hex/index.js @@ -0,0 +1,69 @@ +const URL = require('url'); + +const got = require('../../util/got'); +const hostRules = require('../../util/host-rules'); + +module.exports = { + getPkgReleases, +}; + +function getHostOpts(url) { + const { host } = URL.parse(url); + const opts = hostRules.find({ platform: 'hex', host }, { json: true }); + if (opts && opts.token) { + opts.hearders = { Authorization: opts.token }; + delete opts.token; + } + return opts; +} + +async function getPkgReleases({ lookupName }) { + const hexUrl = `https://hex.pm/api/packages/${lookupName}`; + try { + const opts = getHostOpts(hexUrl); + const res = (await got(hexUrl, { + json: true, + ...opts, + })).body; + if (!(res && res.releases && res.name)) { + logger.warn({ lookupName }, `Received invalid hex package data`); + return null; + } + const result = { + releases: [], + }; + if (res.releases) { + result.releases = res.releases.map(version => ({ + version: version.version, + })); + } + if (res.meta && res.meta.links) { + result.sourceUrl = res.meta.links.Github; + } + result.homepage = res.html_url; + return result; + } catch (err) { + if (err.statusCode === 401) { + logger.info({ lookupName }, `Authorization failure: not authorized`); + logger.debug( + { + err, + }, + 'Authorization error' + ); + return null; + } + if (err.statusCode === 404) { + logger.info({ lookupName }, `Dependency lookup failure: not found`); + logger.debug( + { + err, + }, + 'Package lookup error' + ); + return null; + } + logger.warn({ err, lookupName }, 'hex lookup failure: Unknown error'); + return null; + } +} diff --git a/lib/datasource/index.js b/lib/datasource/index.js index f6942bd0215a4ac4a9d5e6b22fe7eac153a4fcbc..403cbb85e84572f691aa6f408dec7c4d7e88dd18 100644 --- a/lib/datasource/index.js +++ b/lib/datasource/index.js @@ -3,6 +3,7 @@ const versioning = require('../versioning'); const cargo = require('./cargo'); const docker = require('./docker'); +const hex = require('./hex'); const github = require('./github'); const gitlab = require('./gitlab'); const go = require('./go'); @@ -20,6 +21,7 @@ const terraform = require('./terraform'); const datasources = { cargo, docker, + hex, github, gitlab, go, diff --git a/test/_fixtures/hex/certifi.json b/test/_fixtures/hex/certifi.json new file mode 100644 index 0000000000000000000000000000000000000000..dafae0044fc706b49e48cf13862883befca103e0 --- /dev/null +++ b/test/_fixtures/hex/certifi.json @@ -0,0 +1,133 @@ +{ + "docs_html_url": null, + "downloads": { + "all": 7801231, + "day": 4550, + "recent": 1186862, + "week": 107546 + }, + "html_url": "https://hex.pm/packages/certifi", + "inserted_at": "2015-09-10T13:58:43.376194Z", + "meta": { + "description": "CA bundle adapted from Mozilla by https://certifi.io", + "licenses": [ + "BSD" + ], + "links": { + "Github": "https://github.com/certifi/erlang-certifi" + }, + "maintainers": [ + "Benoit Chesneau" + ] + }, + "name": "certifi", + "owners": [ + { + "email": "bchesneau@gmail.com", + "url": "https://hex.pm/api/users/benoitc", + "username": "benoitc" + } + ], + "releases": [ + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/2.5.1", + "version": "2.5.1" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/2.4.2", + "version": "2.4.2" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/2.4.1", + "version": "2.4.1" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/2.3.1", + "version": "2.3.1" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/2.3.0", + "version": "2.3.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/2.2.0", + "version": "2.2.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/2.1.0", + "version": "2.1.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/2.0.0", + "version": "2.0.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/1.2.1", + "version": "1.2.1" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/1.2.0", + "version": "1.2.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/1.1.0", + "version": "1.1.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/1.0.0", + "version": "1.0.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/0.7.0", + "version": "0.7.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/0.6.0", + "version": "0.6.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/0.5.0", + "version": "0.5.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/0.4.0", + "version": "0.4.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/0.3.0", + "version": "0.3.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/0.2.0", + "version": "0.2.0" + }, + { + "has_docs": false, + "url": "https://hex.pm/api/packages/certifi/releases/0.1.1", + "version": "0.1.1" + } + ], + "repository": "hexpm", + "retirements": { + }, + "updated_at": "2019-01-28T20:35:23.869888Z", + "url": "https://hex.pm/api/packages/certifi" +} diff --git a/test/datasource/__snapshots__/hex.spec.js.snap b/test/datasource/__snapshots__/hex.spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..328b0147280ada397b4245d8272b2594e0be48a9 --- /dev/null +++ b/test/datasource/__snapshots__/hex.spec.js.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`datasource/hex getPkgReleases process public repo without auth 1`] = ` +Object { + "homepage": "https://hex.pm/packages/certifi", + "releases": Array [ + Object { + "version": "2.5.1", + }, + Object { + "version": "2.4.2", + }, + Object { + "version": "2.4.1", + }, + Object { + "version": "2.3.1", + }, + Object { + "version": "2.3.0", + }, + Object { + "version": "2.2.0", + }, + Object { + "version": "2.1.0", + }, + Object { + "version": "2.0.0", + }, + Object { + "version": "1.2.1", + }, + Object { + "version": "1.2.0", + }, + Object { + "version": "1.1.0", + }, + Object { + "version": "1.0.0", + }, + Object { + "version": "0.7.0", + }, + Object { + "version": "0.6.0", + }, + Object { + "version": "0.5.0", + }, + Object { + "version": "0.4.0", + }, + Object { + "version": "0.3.0", + }, + Object { + "version": "0.2.0", + }, + Object { + "version": "0.1.1", + }, + ], + "sourceUrl": "https://github.com/certifi/erlang-certifi", +} +`; + +exports[`datasource/hex getPkgReleases processes real data 1`] = ` +Object { + "homepage": "https://hex.pm/packages/certifi", + "releases": Array [ + Object { + "version": "2.5.1", + }, + Object { + "version": "2.4.2", + }, + Object { + "version": "2.4.1", + }, + Object { + "version": "2.3.1", + }, + Object { + "version": "2.3.0", + }, + Object { + "version": "2.2.0", + }, + Object { + "version": "2.1.0", + }, + Object { + "version": "2.0.0", + }, + Object { + "version": "1.2.1", + }, + Object { + "version": "1.2.0", + }, + Object { + "version": "1.1.0", + }, + Object { + "version": "1.0.0", + }, + Object { + "version": "0.7.0", + }, + Object { + "version": "0.6.0", + }, + Object { + "version": "0.5.0", + }, + Object { + "version": "0.4.0", + }, + Object { + "version": "0.3.0", + }, + Object { + "version": "0.2.0", + }, + Object { + "version": "0.1.1", + }, + ], + "sourceUrl": "https://github.com/certifi/erlang-certifi", +} +`; diff --git a/test/datasource/hex.spec.js b/test/datasource/hex.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ba1094575d21cc9542a57b9fd129e216395a8ea0 --- /dev/null +++ b/test/datasource/hex.spec.js @@ -0,0 +1,81 @@ +const fs = require('fs'); +const got = require('../../lib/util/got'); +const hostRules = require('../../lib/util/host-rules'); +const { getPkgReleases } = require('../../lib/datasource/hex'); + +let res1 = fs.readFileSync('test/_fixtures/hex/certifi.json', 'utf8'); +res1 = JSON.parse(res1); + +jest.mock('../../lib/util/got'); +jest.mock('../../lib/util/host-rules'); + +describe('datasource/hex', () => { + describe('getPkgReleases', () => { + beforeEach(() => { + global.repoCache = {}; + }); + it('returns null for empty result', async () => { + got.mockReturnValueOnce(null); + expect( + await getPkgReleases({ lookupName: 'non_existent_package' }) + ).toBeNull(); + }); + it('returns null for missing fields', async () => { + got.mockReturnValueOnce({ res: {} }); + expect( + await getPkgReleases({ lookupName: 'non_existent_package' }) + ).toBeNull(); + }); + it('returns null for 404', async () => { + got.mockImplementationOnce(() => + Promise.reject({ + statusCode: 404, + }) + ); + expect(await getPkgReleases({ lookupName: 'some_package' })).toBeNull(); + }); + it('returns null for 401', async () => { + got.mockImplementationOnce(() => + Promise.reject({ + statusCode: 401, + }) + ); + expect(await getPkgReleases({ lookupName: 'some_package' })).toBeNull(); + }); + it('returns null for unknown error', async () => { + got.mockImplementationOnce(() => { + throw new Error(); + }); + expect(await getPkgReleases('some_package')).toBeNull(); + }); + it('returns null with wrong auth token', async () => { + hostRules.find.mockReturnValueOnce({ token: 'this_simple_token' }); + got.mockReturnValueOnce( + Promise.reject({ + statusCode: 401, + }) + ); + const res = await getPkgReleases({ lookupName: 'certifi' }); + expect(res).toBeNull(); + }); + it('processes real data', async () => { + got.mockReturnValueOnce({ + body: res1, + }); + const res = await getPkgReleases({ lookupName: 'certifi' }); + expect(res).toMatchSnapshot(); + expect(res).not.toBeNull(); + expect(res).toBeDefined(); + }); + it('process public repo without auth', async () => { + hostRules.find.mockReturnValueOnce(null); + got.mockReturnValueOnce({ + body: res1, + }); + const res = await getPkgReleases({ lookupName: 'certifi' }); + expect(res).toMatchSnapshot(); + expect(res).not.toBeNull(); + expect(res).toBeDefined(); + }); + }); +});