Rewrite vso, rename to [AzureDevops], validate SVG [readthedocs] (#2252)
1. Add validation to BaseSvgScrapingService and update readthedocs accordingly. 2. Rewrite vso and add more tests. Rename it internally to azure-devops. URLs are still `/vso` for now. Should we make a way to let a service register multiple URL patterns? 3. Handle shared code using a functional pattern instead of inheritance. This comes from a discussion https://github.com/badges/shields/pull/2031#issuecomment-417893819. I like the functional approach because it's more direct, nimble, and easy to reason about; plus it allows services to grow from a family of one to two more easily.
This commit is contained in:
87
services/azure-devops/azure-devops-build.service.js
Normal file
87
services/azure-devops/azure-devops-build.service.js
Normal file
@@ -0,0 +1,87 @@
|
||||
'use strict'
|
||||
|
||||
const BaseSvgService = require('../base-svg-scraping')
|
||||
const { NotFound } = require('../errors')
|
||||
const { fetch, render } = require('./azure-devops-helpers')
|
||||
|
||||
const documentation = `
|
||||
<p>
|
||||
To obtain your own badge, you need to get 3 pieces of information:
|
||||
<code>ORGANIZATION</code>, <code>PROJECT_ID</code> and <code>DEFINITION_ID</code>.
|
||||
</p>
|
||||
<p>
|
||||
First, you need to edit your build definition and look at the url:
|
||||
</p>
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/3749820/47259976-e2d9ec80-d4b2-11e8-92cc-7c81089a7a2c.png"
|
||||
alt="ORGANIZATION is after the dev.azure.com part, PROJECT_NAME is right after that, DEFINITION_ID is at the end after the id= part." />
|
||||
<p>
|
||||
Then, you can get the <code>PROJECT_ID</code> from the <code>PROJECT_NAME</code> using Azure DevOps REST API.
|
||||
Just access to: <code>https://dev.azure.com/ORGANIZATION/_apis/projects/PROJECT_NAME</code>.
|
||||
</p>
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/3749820/47266325-1d846900-d535-11e8-9211-2ee72fb91877.png"
|
||||
alt="PROJECT_ID is in the id property of the API response." />
|
||||
<p>
|
||||
Your badge will then have the form:
|
||||
<code>https://img.shields.io/vso/build/ORGANIZATION/PROJECT_ID/DEFINITION_ID.svg</code>.
|
||||
</p>
|
||||
<p>
|
||||
Optionally, you can specify a named branch:
|
||||
<code>https://img.shields.io/vso/build/ORGANIZATION/PROJECT_ID/DEFINITION_ID/NAMED_BRANCH.svg</code>.
|
||||
</p>
|
||||
`
|
||||
|
||||
module.exports = class AzureDevOpsBuild extends BaseSvgService {
|
||||
static get category() {
|
||||
return 'build'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return {
|
||||
base: '',
|
||||
format: '(?:azure-devops|vso)/build/([^/]+)/([^/]+)/([^/]+)(?:/(.+))?',
|
||||
capture: ['organization', 'projectId', 'definitionId', 'branch'],
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Azure DevOps builds',
|
||||
urlPattern: 'azure-devops/build/:organization/:projectId/:definitionId',
|
||||
staticExample: render({ status: 'succeeded' }),
|
||||
exampleUrl:
|
||||
'azure-devops/build/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/2',
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'Azure DevOps builds (branch)',
|
||||
urlPattern:
|
||||
'azure-devops/build/:organization/:projectId/:definitionId/:branch',
|
||||
staticExample: render({ status: 'succeeded' }),
|
||||
exampleUrl:
|
||||
'azure-devops/build/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/2/master',
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle({ organization, projectId, definitionId, branch }) {
|
||||
// Microsoft documentation: https://docs.microsoft.com/en-us/rest/api/vsts/build/status/get
|
||||
const { status } = await fetch(this, {
|
||||
url: `https://dev.azure.com/${organization}/${projectId}/_apis/build/status/${definitionId}`,
|
||||
qs: { branchName: branch },
|
||||
errorMessages: {
|
||||
404: 'user or project not found',
|
||||
},
|
||||
})
|
||||
if (status === 'set up now') {
|
||||
throw new NotFound({ prettyMessage: 'definition not found' })
|
||||
}
|
||||
if (status === 'unknown') {
|
||||
throw new NotFound({ prettyMessage: 'project not found' })
|
||||
}
|
||||
return render({ status })
|
||||
}
|
||||
}
|
||||
45
services/azure-devops/azure-devops-build.tester.js
Normal file
45
services/azure-devops/azure-devops-build.tester.js
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { isBuildStatus } = require('../test-validators')
|
||||
const t = require('../create-service-tester')()
|
||||
module.exports = t
|
||||
|
||||
// https://dev.azure.com/totodem/Shields.io is a public Azure DevOps project
|
||||
// solely created for Shields.io testing.
|
||||
|
||||
t.create('default branch')
|
||||
.get(
|
||||
'/azure-devops/build/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/2.json'
|
||||
)
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'build',
|
||||
value: isBuildStatus,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('named branch')
|
||||
.get(
|
||||
'/azure-devops/build/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/2/master.json'
|
||||
)
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'build',
|
||||
value: isBuildStatus,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('unknown definition')
|
||||
.get(
|
||||
'/azure-devops/build/larsbrinkhoff/953a34b9-5966-4923-a48a-c41874cfb5f5/515.json'
|
||||
)
|
||||
.expectJSON({ name: 'build', value: 'definition not found' })
|
||||
|
||||
t.create('unknown project')
|
||||
.get('/azure-devops/build/larsbrinkhoff/foo/515.json')
|
||||
.expectJSON({ name: 'build', value: 'user or project not found' })
|
||||
|
||||
t.create('unknown user')
|
||||
.get('/azure-devops/build/notarealuser/foo/515.json')
|
||||
.expectJSON({ name: 'build', value: 'user or project not found' })
|
||||
46
services/azure-devops/azure-devops-helpers.js
Normal file
46
services/azure-devops/azure-devops-helpers.js
Normal file
@@ -0,0 +1,46 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
|
||||
const schema = Joi.object({
|
||||
message: Joi.equal(
|
||||
'succeeded',
|
||||
'partially suceeded',
|
||||
'failed',
|
||||
'unknown',
|
||||
'set up now'
|
||||
).required(),
|
||||
}).required()
|
||||
|
||||
async function fetch(serviceInstance, { url, qs = {}, errorMessages }) {
|
||||
// Microsoft documentation: https://docs.microsoft.com/en-us/rest/api/vsts/build/status/get
|
||||
const { message: status } = await serviceInstance._requestSvg({
|
||||
schema,
|
||||
url,
|
||||
options: { qs },
|
||||
errorMessages,
|
||||
})
|
||||
return { status }
|
||||
}
|
||||
|
||||
function render({ status }) {
|
||||
switch (status) {
|
||||
case 'succeeded':
|
||||
return {
|
||||
message: 'passing',
|
||||
color: 'brightgreen',
|
||||
}
|
||||
case 'partially succeeded':
|
||||
return {
|
||||
message: 'passing',
|
||||
color: 'orange',
|
||||
}
|
||||
case 'failed':
|
||||
return {
|
||||
message: 'failing',
|
||||
color: 'red',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { fetch, render }
|
||||
69
services/azure-devops/azure-devops-release.service.js
Normal file
69
services/azure-devops/azure-devops-release.service.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict'
|
||||
|
||||
const BaseSvgService = require('../base-svg-scraping')
|
||||
const { fetch, render } = require('./azure-devops-helpers')
|
||||
|
||||
const documentation = `
|
||||
<p>
|
||||
To obtain your own badge, you need to get 4 pieces of information:
|
||||
<code>ORGANIZATION</code>, <code>PROJECT_ID</code>, <code>DEFINITION_ID</code> and <code>ENVIRONMENT_ID</code>.
|
||||
</p>
|
||||
<p>
|
||||
First, you need to enable badges for each required environments in the options of your release definition.
|
||||
Once you have save the change, look at badge url:
|
||||
</p>
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/3749820/47266694-7f939d00-d53a-11e8-9224-c2371dd2d0c9.png"
|
||||
alt="ORGANIZATION is after the dev.azure.com part, PROJECT_ID is after the badge part, DEFINITION_ID and ENVIRONMENT_ID are right after that." />
|
||||
<p>
|
||||
Your badge will then have the form:
|
||||
<code>https://img.shields.io/vso/release/ORGANIZATION/PROJECT_ID/DEFINITION_ID/ENVIRONMENT_ID.svg</code>.
|
||||
</p>
|
||||
`
|
||||
|
||||
module.exports = class AzureDevOpsRelease extends BaseSvgService {
|
||||
static get category() {
|
||||
return 'build'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return {
|
||||
base: '',
|
||||
format: '(?:azure-devops|vso)/release/([^/]+)/([^/]+)/([^/]+)/([^/]+)',
|
||||
capture: ['organization', 'projectId', 'definitionId', 'environmentId'],
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Azure DevOps releases',
|
||||
urlPattern:
|
||||
'azure-devops/release/:organization/:projectId/:definitionId/:environmentId',
|
||||
staticExample: render({ status: 'succeeded' }),
|
||||
exampleUrl:
|
||||
'azure-devops/release/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/1/1',
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'deployment',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ organization, projectId, definitionId, environmentId }) {
|
||||
// Microsoft documentation: ?
|
||||
const props = await fetch(this, {
|
||||
url: `https://vsrm.dev.azure.com/${organization}/_apis/public/Release/badge/${projectId}/${definitionId}/${environmentId}`,
|
||||
errorMessages: {
|
||||
400: 'project not found',
|
||||
404: 'user or environment not found',
|
||||
500: 'inaccessible or definition not found',
|
||||
},
|
||||
})
|
||||
return render(props)
|
||||
}
|
||||
}
|
||||
43
services/azure-devops/azure-devops-release.tester.js
Normal file
43
services/azure-devops/azure-devops-release.tester.js
Normal file
@@ -0,0 +1,43 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { isBuildStatus } = require('../test-validators')
|
||||
const t = require('../create-service-tester')()
|
||||
module.exports = t
|
||||
|
||||
// https://dev.azure.com/totodem/Shields.io is a public Azure DevOps project
|
||||
// solely created for Shields.io testing.
|
||||
|
||||
t.create('release status is succeeded')
|
||||
.get(
|
||||
'/azure-devops/release/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/1/1.json'
|
||||
)
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'deployment',
|
||||
value: isBuildStatus,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('unknown environment')
|
||||
.get(
|
||||
'/azure-devops/release/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/1/515.json'
|
||||
)
|
||||
.expectJSON({ name: 'deployment', value: 'user or environment not found' })
|
||||
|
||||
t.create('unknown definition')
|
||||
.get(
|
||||
'/azure-devops/release/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/515/515.json'
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'deployment',
|
||||
value: 'inaccessible or definition not found',
|
||||
})
|
||||
|
||||
t.create('unknown project')
|
||||
.get('/azure-devops/release/totodem/515/515/515.json')
|
||||
.expectJSON({ name: 'deployment', value: 'project not found' })
|
||||
|
||||
t.create('unknown user')
|
||||
.get('/azure-devops/release/this-repo/does-not-exist/1/2.json')
|
||||
.expectJSON({ name: 'deployment', value: 'user or environment not found' })
|
||||
@@ -26,7 +26,13 @@ class BaseSvgScrapingService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async _requestSvg({ valueMatcher, url, options = {}, errorMessages = {} }) {
|
||||
async _requestSvg({
|
||||
schema,
|
||||
valueMatcher,
|
||||
url,
|
||||
options = {},
|
||||
errorMessages = {},
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
const mergedOptions = {
|
||||
...{ headers: { Accept: 'image/svg+xml' } },
|
||||
@@ -38,8 +44,13 @@ class BaseSvgScrapingService extends BaseService {
|
||||
errorMessages,
|
||||
})
|
||||
logTrace(emojic.dart, 'Response SVG', buffer)
|
||||
const message = this.constructor.valueFromSvgBadge(buffer, valueMatcher)
|
||||
return { message }
|
||||
const data = {
|
||||
message: this.constructor.valueFromSvgBadge(buffer, valueMatcher),
|
||||
}
|
||||
logTrace(emojic.dart, 'Response SVG (before validation)', data, {
|
||||
deep: true,
|
||||
})
|
||||
return this.constructor._validate(data, schema)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const chai = require('chai')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const Joi = require('joi')
|
||||
const { makeBadgeData } = require('../lib/badge-data')
|
||||
const testHelpers = require('../lib/make-badge-test-helpers')
|
||||
const BaseSvgScrapingService = require('./base-svg-scraping')
|
||||
@@ -15,6 +16,10 @@ function makeExampleSvg({ label, message }) {
|
||||
return testHelpers.makeBadge()(badgeData)
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
message: Joi.string().required(),
|
||||
}).required()
|
||||
|
||||
class DummySvgScrapingService extends BaseSvgScrapingService {
|
||||
static get category() {
|
||||
return 'cat'
|
||||
@@ -28,6 +33,7 @@ class DummySvgScrapingService extends BaseSvgScrapingService {
|
||||
|
||||
async handle() {
|
||||
return this._requestSvg({
|
||||
schema,
|
||||
url: 'http://example.com/foo.svg',
|
||||
})
|
||||
}
|
||||
@@ -79,6 +85,7 @@ describe('BaseSvgScrapingService', function() {
|
||||
Object.assign(serviceInstance, {
|
||||
async handle() {
|
||||
const { value } = await this._requestSvg({
|
||||
schema,
|
||||
url: 'http://example.com/foo.svg',
|
||||
options: {
|
||||
method: 'POST',
|
||||
@@ -130,6 +137,7 @@ describe('BaseSvgScrapingService', function() {
|
||||
Object.assign(serviceInstance, {
|
||||
async handle() {
|
||||
return this._requestSvg({
|
||||
schema,
|
||||
valueMatcher: />([^<>]+)<\/desc>/,
|
||||
url: 'http://example.com/foo.svg',
|
||||
})
|
||||
|
||||
@@ -4,20 +4,16 @@ const caller = require('caller')
|
||||
const ServiceTester = require('./service-tester')
|
||||
const BaseService = require('./base')
|
||||
|
||||
// Automatically create a ServiceTester. When run from e.g.
|
||||
// `gem-rank.tester.js`, this will create a tester that attaches to the
|
||||
// service found in `gem-rank.service.js`.
|
||||
// Automatically create a ServiceTester.
|
||||
//
|
||||
// When run from e.g. `gem-rank.tester.js`, this will create a tester that
|
||||
// attaches to the service found in `gem-rank.service.js`.
|
||||
//
|
||||
// This can't be used for `.service.js` files which export more than one
|
||||
// service.
|
||||
function createServiceTester() {
|
||||
const servicePath = caller().replace('.tester.js', '.service.js')
|
||||
let ServiceClass
|
||||
try {
|
||||
ServiceClass = require(servicePath)
|
||||
} catch (e) {
|
||||
throw Error(`Couldn't load service from ${servicePath}`)
|
||||
}
|
||||
const ServiceClass = require(servicePath)
|
||||
if (!(ServiceClass.prototype instanceof BaseService)) {
|
||||
throw Error(
|
||||
`${servicePath} does not export a single service. Invoke new ServiceTester() directly.`
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const BaseSvgScrapingService = require('../base-svg-scraping')
|
||||
const { NotFound } = require('../errors')
|
||||
|
||||
const schema = Joi.object({
|
||||
message: Joi.string().required(),
|
||||
}).required()
|
||||
|
||||
module.exports = class ReadTheDocs extends BaseSvgScrapingService {
|
||||
static get category() {
|
||||
return 'build'
|
||||
@@ -60,6 +65,7 @@ module.exports = class ReadTheDocs extends BaseSvgScrapingService {
|
||||
|
||||
async handle({ project, version }) {
|
||||
const { message: status } = await this._requestSvg({
|
||||
schema,
|
||||
url: `https://readthedocs.org/projects/${encodeURIComponent(
|
||||
project
|
||||
)}/badge/`,
|
||||
|
||||
@@ -31,7 +31,7 @@ class ServiceTester {
|
||||
|
||||
static forServiceClass(ServiceClass) {
|
||||
const id = ServiceClass.name
|
||||
const pathPrefix = `/${ServiceClass.url.base}`
|
||||
const pathPrefix = ServiceClass.url.base ? `/${ServiceClass.url.base}` : ''
|
||||
return new this({
|
||||
id,
|
||||
title: id,
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const LegacyService = require('../legacy-service')
|
||||
const { fetchFromSvg } = require('../../lib/svg-badge-parser')
|
||||
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
|
||||
|
||||
const devOpsBuildDoc = `
|
||||
<p>
|
||||
To obtain your own badge, you need to get 3 pieces of information:
|
||||
<code>ORGANIZATION</code>, <code>PROJECT_ID</code> and <code>DEFINITION_ID</code>.
|
||||
</p>
|
||||
<p>
|
||||
First, you need to edit your build definition and look at the url:
|
||||
</p>
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/3749820/47259976-e2d9ec80-d4b2-11e8-92cc-7c81089a7a2c.png"
|
||||
alt="ORGANIZATION is after the dev.azure.com part, PROJECT_NAME is right after that, DEFINITION_ID is at the end after the id= part." />
|
||||
<p>
|
||||
Then, you can get the <code>PROJECT_ID</code> from the <code>PROJECT_NAME</code> using Azure DevOps REST API.
|
||||
Just access to: <code>https://dev.azure.com/ORGANIZATION/_apis/projects/PROJECT_NAME</code>.
|
||||
</p>
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/3749820/47266325-1d846900-d535-11e8-9211-2ee72fb91877.png"
|
||||
alt="PROJECT_ID is in the id property of the API response." />
|
||||
<p>
|
||||
Your badge will then have the form:
|
||||
<code>https://img.shields.io/vso/build/ORGANIZATION/PROJECT_ID/DEFINITION_ID.svg</code>.
|
||||
</p>
|
||||
<p>
|
||||
Optionally, you can specify a named branch:
|
||||
<code>https://img.shields.io/vso/build/ORGANIZATION/PROJECT_ID/DEFINITION_ID/NAMED_BRANCH.svg</code>.
|
||||
</p>
|
||||
`
|
||||
|
||||
const devOpsReleaseDoc = `
|
||||
<p>
|
||||
To obtain your own badge, you need to get 4 pieces of information:
|
||||
<code>ORGANIZATION</code>, <code>PROJECT_ID</code>, <code>DEFINITION_ID</code> and <code>ENVIRONMENT_ID</code>.
|
||||
</p>
|
||||
<p>
|
||||
First, you need to enable badges for each required environments in the options of your release definition.
|
||||
Once you have save the change, look at badge url:
|
||||
</p>
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/3749820/47266694-7f939d00-d53a-11e8-9224-c2371dd2d0c9.png"
|
||||
alt="ORGANIZATION is after the dev.azure.com part, PROJECT_ID is after the badge part, DEFINITION_ID and ENVIRONMENT_ID are right after that." />
|
||||
<p>
|
||||
Your badge will then have the form:
|
||||
<code>https://img.shields.io/vso/release/ORGANIZATION/PROJECT_ID/DEFINITION_ID/ENVIRONMENT_ID.svg</code>.
|
||||
</p>
|
||||
`
|
||||
|
||||
const fetchVstsBadge = (request, url, badgeData, sendBadge, format) => {
|
||||
fetchFromSvg(request, url, />([^<>]+)<\/text><\/g>/, (err, res) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = 'inaccessible'
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
try {
|
||||
badgeData.text[1] = res.toLowerCase()
|
||||
if (res === 'succeeded') {
|
||||
badgeData.colorscheme = 'brightgreen'
|
||||
badgeData.text[1] = 'passing'
|
||||
} else if (res === 'partially succeeded') {
|
||||
badgeData.colorscheme = 'orange'
|
||||
badgeData.text[1] = 'passing'
|
||||
} else if (res === 'failed') {
|
||||
badgeData.colorscheme = 'red'
|
||||
badgeData.text[1] = 'failing'
|
||||
}
|
||||
sendBadge(format, badgeData)
|
||||
} catch (e) {
|
||||
badgeData.text[1] = 'invalid'
|
||||
sendBadge(format, badgeData)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = class Vso extends LegacyService {
|
||||
static get category() {
|
||||
return 'build'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return {
|
||||
base: 'vso',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Azure DevOps builds',
|
||||
previewUrl: 'build/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/2',
|
||||
documentation: devOpsBuildDoc,
|
||||
},
|
||||
{
|
||||
title: 'Azure DevOps releases',
|
||||
previewUrl: 'release/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/1/1',
|
||||
documentation: devOpsReleaseDoc,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler({ camp, cache }) {
|
||||
// For Azure DevOps builds.
|
||||
camp.route(
|
||||
/^\/vso\/build\/([^/]+)\/([^/]+)\/([^/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
// Microsoft documentation: https://docs.microsoft.com/en-us/rest/api/vsts/build/status/get
|
||||
const organization = match[1] // The name (string) of the Azure DevOps organization.
|
||||
const projectId = match[2] // The ID (uuid) of the project.
|
||||
const definitionId = match[3] // The ID (int) of the definition.
|
||||
const branchName = match[4] // The name (string) of the branch.
|
||||
const format = match[5]
|
||||
let url = `https://dev.azure.com/${organization}/${projectId}/_apis/build/status/${definitionId}`
|
||||
if (branchName != null) {
|
||||
url += `?branchName=${branchName}`
|
||||
}
|
||||
const badgeData = getBadgeData('build', data)
|
||||
fetchVstsBadge(request, url, badgeData, sendBadge, format)
|
||||
})
|
||||
)
|
||||
|
||||
// For Azure DevOps releases.
|
||||
camp.route(
|
||||
/^\/vso\/release\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
// Microsoft documentation: ?
|
||||
const organization = match[1] // The name (string) of the Azure DevOps organization.
|
||||
const projectId = match[2] // The ID (uuid) of the project.
|
||||
const definitionId = match[3] // The ID (int) of the definition.
|
||||
const environmentId = match[4] // The ID (int) of the release environment.
|
||||
const format = match[5]
|
||||
const url = `https://vsrm.dev.azure.com/${organization}/_apis/public/Release/badge/${projectId}/${definitionId}/${environmentId}`
|
||||
const badgeData = getBadgeData('deployment', data)
|
||||
fetchVstsBadge(request, url, badgeData, sendBadge, format)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const ServiceTester = require('../service-tester')
|
||||
|
||||
const isValidStatus = Joi.equal('passing', 'failing')
|
||||
|
||||
const t = new ServiceTester({
|
||||
id: 'vso',
|
||||
title: 'Visual Studio Team Services',
|
||||
})
|
||||
module.exports = t
|
||||
|
||||
// https://dev.azure.com/totodem/Shields.io is a public Azure DevOps project solely created for Shield.io testing
|
||||
|
||||
// Builds
|
||||
|
||||
t.create('build status on default branch')
|
||||
.get('/build/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/2.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'build',
|
||||
value: isValidStatus,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('build status on named branch')
|
||||
.get('/build/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/2/master.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'build',
|
||||
value: isValidStatus,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('build status on unknown repo')
|
||||
.get('/build/this-repo/does-not/exist.json')
|
||||
.expectJSON({ name: 'build', value: 'inaccessible' })
|
||||
|
||||
t.create('build status with connection error')
|
||||
.get('/build/foo/bar/foobar.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'build', value: 'inaccessible' })
|
||||
|
||||
// Releases
|
||||
|
||||
t.create('release status is succeeded')
|
||||
.get('/release/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/1/1.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'deployment',
|
||||
value: isValidStatus,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('release status on unknown repo')
|
||||
.get('/release/this-repo/does-not-exist/1/2.json')
|
||||
.expectJSON({ name: 'deployment', value: 'inaccessible' })
|
||||
|
||||
t.create('release status with connection error')
|
||||
.get('/release/foo/bar/1/2.json')
|
||||
.networkOff()
|
||||
.expectJSON({ name: 'deployment', value: 'inaccessible' })
|
||||
Reference in New Issue
Block a user