From a158cf858b31a7d8ed1de06294051bfaccfa1de2 Mon Sep 17 00:00:00 2001 From: Pierre-Yves B Date: Tue, 8 Jan 2019 19:33:14 +0000 Subject: [PATCH] [Travis-Build] service rewrite, run [travis-php-version] (#2660) * Rewrote Travis-Build service and separated tests * Fixed property shorthand * Strenghtened schema validation * Implemented keyword remapping --- lib/build-status.js | 50 ++++++++++ lib/build-status.spec.js | 69 +++++++++++++ services/travis/travis-build.service.js | 98 +++++++------------ ...ravis.tester.js => travis-build.tester.js} | 60 +++--------- services/travis/travis-php-version.tester.js | 33 +++++++ 5 files changed, 201 insertions(+), 109 deletions(-) create mode 100644 lib/build-status.js create mode 100644 lib/build-status.spec.js rename services/travis/{travis.tester.js => travis-build.tester.js} (51%) create mode 100644 services/travis/travis-php-version.tester.js diff --git a/lib/build-status.js b/lib/build-status.js new file mode 100644 index 0000000000..b1d68e54ff --- /dev/null +++ b/lib/build-status.js @@ -0,0 +1,50 @@ +'use strict' + +const Joi = require('joi') + +const happyStatuses = ['passed', 'passing', 'success'] + +const unhappyStatuses = ['error', 'failed', 'failing', 'unstable'] + +const otherStatuses = [ + 'building', + 'cancelled', + 'expired', + 'no tests', + 'not built', + 'not run', + 'pending', + 'processing', + 'queued', + 'running', + 'scheduled', + 'skipped', + 'stopped', + 'timeout', + 'waiting', +] + +const isBuildStatus = Joi.equal( + happyStatuses.concat(unhappyStatuses).concat(otherStatuses) +) + +function renderBuildStatusBadge({ label, status }) { + let message + let color + if (happyStatuses.includes(status)) { + message = 'passing' + color = 'brightgreen' + } else if (unhappyStatuses.includes(status)) { + message = status === 'failed' ? 'failing' : status + color = 'red' + } else { + message = status + } + return { + label, + message, + color, + } +} + +module.exports = { isBuildStatus, renderBuildStatusBadge } diff --git a/lib/build-status.spec.js b/lib/build-status.spec.js new file mode 100644 index 0000000000..d26be11b15 --- /dev/null +++ b/lib/build-status.spec.js @@ -0,0 +1,69 @@ +'use strict' + +const { expect } = require('chai') +const { test, given, forCases } = require('sazerac') +const { renderBuildStatusBadge } = require('./build-status') + +test(renderBuildStatusBadge, () => { + given({ label: 'build', status: 'passed' }).expect({ + label: 'build', + message: 'passing', + color: 'brightgreen', + }) + given({ label: 'build', status: 'success' }).expect({ + label: 'build', + message: 'passing', + color: 'brightgreen', + }) + given({ label: 'build', status: 'failed' }).expect({ + label: 'build', + message: 'failing', + color: 'red', + }) + given({ label: 'build', status: 'error' }).expect({ + label: 'build', + message: 'error', + color: 'red', + }) +}) + +test(renderBuildStatusBadge, () => { + forCases([ + given({ status: 'passed' }), + given({ status: 'passing' }), + given({ status: 'success' }), + ]).assert('should be brightgreen', b => + expect(b).to.include({ color: 'brightgreen' }) + ) +}) + +test(renderBuildStatusBadge, () => { + forCases([ + given({ status: 'error' }), + given({ status: 'failed' }), + given({ status: 'failing' }), + given({ status: 'unstable' }), + ]).assert('should be red', b => expect(b).to.include({ color: 'red' })) +}) + +test(renderBuildStatusBadge, () => { + forCases([ + given({ status: 'building' }), + given({ status: 'cancelled' }), + given({ status: 'expired' }), + given({ status: 'no tests' }), + given({ status: 'not built' }), + given({ status: 'not run' }), + given({ status: 'pending' }), + given({ status: 'processing' }), + given({ status: 'queued' }), + given({ status: 'running' }), + given({ status: 'scheduled' }), + given({ status: 'skipped' }), + given({ status: 'stopped' }), + given({ status: 'timeout' }), + given({ status: 'waiting' }), + ]).assert('should have undefined color', b => + expect(b).to.include({ color: undefined }) + ) +}) diff --git a/services/travis/travis-build.service.js b/services/travis/travis-build.service.js index e28bb2fda4..9e640ab952 100644 --- a/services/travis/travis-build.service.js +++ b/services/travis/travis-build.service.js @@ -1,19 +1,20 @@ 'use strict' -const LegacyService = require('../legacy-service') -const { makeBadgeData: getBadgeData } = require('../../lib/badge-data') -const { checkErrorResponse } = require('../../lib/error-helper') -const log = require('../../lib/log') +const Joi = require('joi') -// Handle .org and .com. -// -// 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 TravisBuild extends LegacyService { +const BaseSvgScrapingService = require('../base-svg-scraping') +const { + isBuildStatus, + renderBuildStatusBadge, +} = require('../../lib/build-status') + +const schema = Joi.object({ + message: Joi.alternatives() + .try(isBuildStatus, Joi.equal('unknown')) + .required(), +}).required() + +module.exports = class TravisBuild extends BaseSvgScrapingService { static get category() { return 'build' } @@ -21,6 +22,8 @@ module.exports = class TravisBuild extends LegacyService { static get route() { return { base: 'travis', + format: '(?:(com)/)?(?!php-v)([^/]+/[^/]+)(?:/(.+))?', + capture: ['comDomain', 'userRepo', 'branch'], } } @@ -62,54 +65,25 @@ module.exports = class TravisBuild extends LegacyService { return { message: 'passing', color: 'brightgreen' } } - static registerLegacyRouteHandler({ camp, cache }) { - camp.route( - /^\/travis(-ci)?\/(?:(com)\/)?(?!php-v)([^/]+\/[^/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/, - cache((data, match, sendBadge, request) => { - const travisDomain = match[2] || 'org' // (com | org) org by default - const userRepo = match[3] // eg, espadrine/sc - const branch = match[4] - const format = match[5] - const options = { - method: 'HEAD', - uri: `https://api.travis-ci.${travisDomain}/${userRepo}.svg`, - } - if (branch != null) { - options.uri += `?branch=${branch}` - } - const badgeData = getBadgeData('build', data) - request(options, (err, res) => { - if (err != null) { - log.error( - `Travis error: data:${JSON.stringify(data)}\nStack: ${err.stack}` - ) - if (res) { - log.error(`${res}`) - } - } - if (checkErrorResponse(badgeData, err, res)) { - sendBadge(format, badgeData) - return - } - try { - const state = res.headers['content-disposition'].match( - /filename="(.+)\.svg"/ - )[1] - badgeData.text[1] = state - if (state === 'passing') { - badgeData.colorscheme = 'brightgreen' - } else if (state === 'failing') { - badgeData.colorscheme = 'red' - } else { - badgeData.text[1] = state - } - sendBadge(format, badgeData) - } catch (e) { - badgeData.text[1] = 'invalid' - sendBadge(format, badgeData) - } - }) - }) - ) + static get defaultBadgeData() { + return { + label: 'build', + } + } + + static render({ status }) { + return renderBuildStatusBadge({ status }) + } + + async handle({ comDomain, userRepo, branch }) { + const domain = comDomain || 'org' + const { message: status } = await this._requestSvg({ + schema, + url: `https://api.travis-ci.${domain}/${userRepo}.svg`, + options: { qs: { branch } }, + valueMatcher: />([^<>]+)<\/text><\/g>/, + }) + + return this.constructor.render({ status }) } } diff --git a/services/travis/travis.tester.js b/services/travis/travis-build.tester.js similarity index 51% rename from services/travis/travis.tester.js rename to services/travis/travis-build.tester.js index ad3c4a3449..e6397578ea 100644 --- a/services/travis/travis.tester.js +++ b/services/travis/travis-build.tester.js @@ -2,15 +2,15 @@ const Joi = require('joi') const ServiceTester = require('../service-tester') -const { isBuildStatus, isPhpVersionReduction } = require('../test-validators') +const { isBuildStatus } = require('../test-validators') -const t = new ServiceTester({ - id: 'travis', - title: 'Travis CI/PHP version from .travis.yml', -}) -module.exports = t +const t = (module.exports = new ServiceTester({ + id: 'travis-build', + title: 'Travis CI', + pathPrefix: '/travis', +})) -// Travis CI +// Travis (.org) CI t.create('build status on default branch') .get('/rust-lang/rust.json') @@ -34,19 +34,14 @@ t.create('unknown repo') .get('/this-repo/does-not-exist.json') .expectJSON({ name: 'build', value: 'unknown' }) -t.create('missing content-disposition header') +t.create('invalid svg response') .get('/foo/bar.json') .intercept(nock => nock('https://api.travis-ci.org') - .head('/foo/bar.svg') + .get('/foo/bar.svg') .reply(200) ) - .expectJSON({ name: 'build', value: 'invalid' }) - -t.create('connection error') - .get('/foo/bar.json') - .networkOff() - .expectJSON({ name: 'build', value: 'inaccessible' }) + .expectJSON({ name: 'build', value: 'unparseable svg response' }) // Travis (.com) CI @@ -72,40 +67,11 @@ t.create('unknown repo') .get('/com/this-repo/does-not-exist.json') .expectJSON({ name: 'build', value: 'unknown' }) -t.create('missing content-disposition header') +t.create('invalid svg response') .get('/com/foo/bar.json') .intercept(nock => nock('https://api.travis-ci.com') - .head('/foo/bar.svg') + .get('/foo/bar.svg') .reply(200) ) - .expectJSON({ name: 'build', value: 'invalid' }) - -t.create('connection error') - .get('/com/foo/bar.json') - .networkOff() - .expectJSON({ name: 'build', value: 'inaccessible' }) - -// php version from .travis.yml - -t.create('gets the package version of symfony') - .get('/php-v/symfony/symfony.json') - .expectJSONTypes( - Joi.object().keys({ name: 'php', value: isPhpVersionReduction }) - ) - -t.create('gets the package version of symfony 2.8') - .get('/php-v/symfony/symfony/2.8.json') - .expectJSONTypes( - Joi.object().keys({ name: 'php', value: isPhpVersionReduction }) - ) - -t.create('gets the package version of yii') - .get('/php-v/yiisoft/yii.json') - .expectJSONTypes( - Joi.object().keys({ name: 'php', value: isPhpVersionReduction }) - ) - -t.create('invalid package name') - .get('/php-v/frodo/is-not-a-package.json') - .expectJSON({ name: 'php', value: 'invalid' }) + .expectJSON({ name: 'build', value: 'unparseable svg response' }) diff --git a/services/travis/travis-php-version.tester.js b/services/travis/travis-php-version.tester.js new file mode 100644 index 0000000000..b6e52becfe --- /dev/null +++ b/services/travis/travis-php-version.tester.js @@ -0,0 +1,33 @@ +'use strict' + +const Joi = require('joi') +const ServiceTester = require('../service-tester') +const { isPhpVersionReduction } = require('../test-validators') + +const t = (module.exports = new ServiceTester({ + id: 'travis-php-version', + title: 'PHP version from .travis.yml', + pathPrefix: '/travis', +})) + +t.create('gets the package version of symfony') + .get('/php-v/symfony/symfony.json') + .expectJSONTypes( + Joi.object().keys({ name: 'php', value: isPhpVersionReduction }) + ) + +t.create('gets the package version of symfony 2.8') + .get('/php-v/symfony/symfony/2.8.json') + .expectJSONTypes( + Joi.object().keys({ name: 'php', value: isPhpVersionReduction }) + ) + +t.create('gets the package version of yii') + .get('/php-v/yiisoft/yii.json') + .expectJSONTypes( + Joi.object().keys({ name: 'php', value: isPhpVersionReduction }) + ) + +t.create('invalid package name') + .get('/php-v/frodo/is-not-a-package.json') + .expectJSON({ name: 'php', value: 'invalid' })