[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:
50
lib/build-status.js
Normal file
50
lib/build-status.js
Normal 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
69
lib/build-status.spec.js
Normal 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 })
|
||||
)
|
||||
})
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' })
|
||||
33
services/travis/travis-php-version.tester.js
Normal file
33
services/travis/travis-php-version.tester.js
Normal 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' })
|
||||
Reference in New Issue
Block a user