From cad3ea434fab4b4c0b6c30f4a67a4466740de69f Mon Sep 17 00:00:00 2001 From: Caleb Cartwright Date: Sat, 2 Mar 2019 04:48:38 -0600 Subject: [PATCH] Refactor [Coveralls] (#3130) * refactor(coveralls) * chore: added comment with link to api * doc: updated coveralls api doc comment * doc: updated coveralls api doc comment --- services/coveralls/coveralls.service.js | 130 +++++++++------------- services/coveralls/coveralls.tester.js | 141 ++---------------------- 2 files changed, 64 insertions(+), 207 deletions(-) diff --git a/services/coveralls/coveralls.service.js b/services/coveralls/coveralls.service.js index 0abef1ae48..7f10e126d4 100644 --- a/services/coveralls/coveralls.service.js +++ b/services/coveralls/coveralls.service.js @@ -1,54 +1,63 @@ 'use strict' -const LegacyService = require('../legacy-service') -const { makeBadgeData: getBadgeData } = require('../../lib/badge-data') -const { - coveragePercentage: coveragePercentageColor, -} = require('../color-formatters') +const Joi = require('joi') +const { BaseJsonService } = require('..') +const { coveragePercentage } = require('../color-formatters') -// This legacy service should be rewritten to use e.g. BaseJsonService. -// -// Tips for rewriting: -// https://github.com/badges/shields/blob/master/doc/rewriting-services.md -// -// Do not base new services on this code. -module.exports = class Coveralls extends LegacyService { +const schema = Joi.object({ + covered_percent: Joi.number() + .min(0) + .max(100) + .required(), +}).required() + +module.exports = class Coveralls extends BaseJsonService { static get category() { return 'coverage' } + static get defaultBadgeData() { + return { label: 'coverage' } + } + + static render({ coverage }) { + return { + message: `${coverage.toFixed(0)}%`, + color: coveragePercentage(coverage), + } + } + static get route() { return { base: 'coveralls', - pattern: '', + pattern: ':vcsType(github|bitbucket)?/:user/:repo/:branch*', } } static get examples() { - const { staticPreview } = this return [ { title: 'Coveralls github', pattern: ':vcsType/:user/:repo', namedParams: { vcsType: 'github', user: 'jekyll', repo: 'jekyll' }, - staticPreview, + staticPreview: this.render({ coverage: 86 }), }, { title: 'Coveralls github branch', pattern: ':vcsType/:user/:repo/:branch', namedParams: { vcsType: 'github', - user: 'jekyll', - repo: 'jekyll', + user: 'lemurheavy', + repo: 'coveralls-ruby', branch: 'master', }, - staticPreview, + staticPreview: this.render({ coverage: 91.81 }), }, { title: 'Coveralls bitbucket', pattern: ':vcsType/:user/:repo', namedParams: { vcsType: 'bitbucket', user: 'pyKLIP', repo: 'pyklip' }, - staticPreview, + staticPreview: this.render({ coverage: 86 }), }, { title: 'Coveralls bitbucket branch', @@ -59,68 +68,39 @@ module.exports = class Coveralls extends LegacyService { repo: 'pyklip', branch: 'master', }, - staticPreview, + staticPreview: this.render({ coverage: 96 }), }, ] } - static get staticPreview() { - return { message: '83%', color: 'yellowgreen' } - } - - static get defaultBadgeData() { - return { - label: 'coverage', + async fetch({ vcsType, user, repo, branch }) { + // https://docs.coveralls.io/api-introduction#getting-data-from-coveralls + const url = `https://coveralls.io/${vcsType || + 'github'}/${user}/${repo}.json` + const options = { + qs: { + // The API returns the latest result (across any branch) if no branch is explicitly specified, + // whereas the Coveralls native badge (and the Shields.io badges for Coveralls) show + // the coverage for the default branch if no branch is explicitly specified. If the user + // doesn't specify their desired badge, then we can get the Coverage for the latest branch + // from the API by specifying an invalid branch name in which case the API returns the coverage + // for the default branch. This ensures we show the same percentage value. + branch: branch || '@', + }, } + + return this._requestJson({ + schema, + url, + options, + errorMessages: { + 404: 'repository not found', + }, + }) } - static registerLegacyRouteHandler({ camp, cache }) { - camp.route( - /^\/coveralls\/(?:(bitbucket|github)\/)?([^/]+\/[^/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/, - cache((data, match, sendBadge, request) => { - const repoService = match[1] ? match[1] : 'github' - const userRepo = match[2] // eg, `jekyll/jekyll`. - const branch = match[3] - const format = match[4] - const apiUrl = { - url: `https://coveralls.io/repos/${repoService}/${userRepo}/badge.svg`, - followRedirect: false, - method: 'HEAD', - } - if (branch) { - apiUrl.url += `?branch=${branch}` - } - const badgeData = getBadgeData('coverage', data) - request(apiUrl, (err, res) => { - if (err != null) { - badgeData.text[1] = 'invalid' - sendBadge(format, badgeData) - return - } - // We should get a 302. Look inside the Location header. - const buffer = res.headers.location - if (!buffer) { - badgeData.text[1] = 'invalid' - sendBadge(format, badgeData) - return - } - try { - const score = buffer.split('_')[1].split('.')[0] - const percentage = parseInt(score) - if (Number.isNaN(percentage)) { - badgeData.text[1] = 'unknown' - sendBadge(format, badgeData) - return - } - badgeData.text[1] = `${score}%` - badgeData.colorscheme = coveragePercentageColor(percentage) - sendBadge(format, badgeData) - } catch (e) { - badgeData.text[1] = 'malformed' - sendBadge(format, badgeData) - } - }) - }) - ) + async handle({ vcsType, user, repo, branch }) { + const json = await this.fetch({ vcsType, user, repo, branch }) + return this.constructor.render({ coverage: json.covered_percent }) } } diff --git a/services/coveralls/coveralls.tester.js b/services/coveralls/coveralls.tester.js index e9ad4b7075..9f166002d0 100644 --- a/services/coveralls/coveralls.tester.js +++ b/services/coveralls/coveralls.tester.js @@ -1,143 +1,20 @@ 'use strict' const { isIntegerPercentage } = require('../test-validators') -const { ServiceTester } = require('../tester') - -const t = (module.exports = new ServiceTester({ - id: 'coveralls', - title: 'Coveralls.io', -})) - -t.create('error status code - location header is missing') - .get('/github/not/existed.json') - .intercept(nock => - nock('https://coveralls.io') - .head('/repos/github/not/existed/badge.svg') - .reply(404) - ) - .expectBadge({ label: 'coverage', message: 'invalid' }) - -t.create('malformed location') - .get('/github/user/repository.json') - .intercept(nock => - nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg') - .reply( - 302, - {}, - { - Location: - 'https://s3.amazonaws.com/assets.coveralls.io/badges/malformedlocation.svg', - } - ) - ) - .expectBadge({ label: 'coverage', message: 'malformed' }) - -t.create('NaN percentage in location') - .get('/github/user/repository.json') - .intercept(nock => - nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg') - .reply( - 302, - {}, - { - Location: - 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_notanumber.svg', - } - ) - ) - .expectBadge({ label: 'coverage', message: 'unknown' }) - -t.create('connection error') - .get('/github/user/repository.json') - .networkOff() - .expectBadge({ label: 'coverage', message: 'invalid' }) - -t.create('show coverage') - .get('/github/user/repository.json') - .intercept(nock => - nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg') - .reply( - 302, - {}, - { - Location: - 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', - } - ) - ) - .expectBadge({ label: 'coverage', message: '50%' }) - -t.create('show coverage for legacy github link') - .get('/user/repository.json') - .intercept(nock => - nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg') - .reply( - 302, - {}, - { - Location: - 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', - } - ) - ) - .expectBadge({ label: 'coverage', message: '50%' }) - -t.create('show coverage for branch') - .get('/github/user/repository/branch.json') - .intercept(nock => - nock('https://coveralls.io') - .head('/repos/github/user/repository/badge.svg?branch=branch') - .reply( - 302, - {}, - { - Location: - 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', - } - ) - ) - .expectBadge({ label: 'coverage', message: '50%' }) - -t.create('show coverage for bitbucket') - .get('/bitbucket/user/repository.json') - .intercept(nock => - nock('https://coveralls.io') - .head('/repos/bitbucket/user/repository/badge.svg') - .reply( - 302, - {}, - { - Location: - 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', - } - ) - ) - .expectBadge({ label: 'coverage', message: '50%' }) - -t.create('show coverage for bitbucket with branch') - .get('/bitbucket/user/repository/branch.json') - .intercept(nock => - nock('https://coveralls.io') - .head('/repos/bitbucket/user/repository/badge.svg?branch=branch') - .reply( - 302, - {}, - { - Location: - 'https://s3.amazonaws.com/assets.coveralls.io/badges/coveralls_50.svg', - } - ) - ) - .expectBadge({ label: 'coverage', message: '50%' }) +const t = (module.exports = require('../tester').createServiceTester()) t.create('github coverage') .get('/github/jekyll/jekyll.json') .expectBadge({ label: 'coverage', message: isIntegerPercentage }) +t.create('nonexistent project') + .get('/github/fake-shields-io/not-a-real-repository.json') + .expectBadge({ label: 'coverage', message: 'repository not found' }) + +t.create('github branch coverage') + .get('/github/lemurheavy/coveralls-ruby/master.json') + .expectBadge({ label: 'coverage', message: isIntegerPercentage }) + t.create('github coverage for legacy link') .get('/jekyll/jekyll.json') .expectBadge({ label: 'coverage', message: isIntegerPercentage })