[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
This commit is contained in:
Pierre-Yves B
2019-01-08 19:33:14 +00:00
committed by GitHub
parent 23ed74e850
commit a158cf858b
5 changed files with 201 additions and 109 deletions

50
lib/build-status.js Normal file
View File

@@ -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 }

69
lib/build-status.spec.js Normal file
View File

@@ -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 })
)
})

View File

@@ -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 })
}
}

View File

@@ -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' })

View File

@@ -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' })