-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Squashed commit: [f6cdac8] Oops, forgot server does not work in Node 9 [3046d79] Clear some more build errors [dbd12fc] Get lint passing with async/await [0a2deeb] Remove unneeded load-logos changes [1b9ae44] Code review comments, and add basic unit test [1593ea0] New API for registering services - v2
- Loading branch information
Showing
9 changed files
with
272 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
parserOptions: | ||
ecmaVersion: 8 | ||
|
||
env: | ||
node: true | ||
# We use Promise, Map, and occasional ES6 syntax. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
'use strict'; | ||
|
||
const BaseService = require('./base'); | ||
|
||
/** | ||
* AppVeyor CI integration. | ||
*/ | ||
module.exports = class AppVeyor extends BaseService { | ||
async handle({repo, branch}) { | ||
let apiUrl = 'https://ci.appveyor.com/api/projects/' + repo; | ||
if (branch != null) { | ||
apiUrl += '/branch/' + branch; | ||
} | ||
const {buffer, res} = await this._sendAndCacheRequest(apiUrl, { | ||
headers: { 'Accept': 'application/json' } | ||
}); | ||
|
||
if (res.statusCode === 404) { | ||
return {text: 'project not found or access denied'}; | ||
} | ||
|
||
const data = JSON.parse(buffer); | ||
const status = data.build.status; | ||
if (status === 'success') { | ||
return {text: 'passing', colorscheme: 'brightgreen'}; | ||
} else if (status !== 'running' && status !== 'queued') { | ||
return {text: 'failing', colorscheme: 'red'}; | ||
} else { | ||
return {text: status}; | ||
} | ||
} | ||
|
||
// Metadata | ||
static get category() { | ||
return 'build'; | ||
} | ||
|
||
static get uri() { | ||
return { | ||
format: '/appveyor/ci/([^/]+/[^/]+)(?:/(.+))?', | ||
capture: ['repo', 'branch'] | ||
}; | ||
} | ||
|
||
static getExamples() { | ||
return [ | ||
{ | ||
uri: '/appveyor/ci/gruntjs/grunt', | ||
}, | ||
{ | ||
name: 'Branch', | ||
uri: '/appveyor/ci/gruntjs/grunt/master', | ||
}, | ||
]; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
'use strict'; | ||
|
||
const { | ||
makeBadgeData: getBadgeData, | ||
} = require('../lib/badge-data'); | ||
|
||
module.exports = class BaseService { | ||
constructor({sendAndCacheRequest}) { | ||
this._sendAndCacheRequest = sendAndCacheRequest; | ||
} | ||
|
||
/** | ||
* Asynchronous function to handle requests for this service. Takes the URI | ||
* parameters (as defined in the `uri` property), performs a request using | ||
* `this._sendAndCacheRequest`, and returns the badge data. | ||
*/ | ||
async handle(namedParams) { | ||
throw new Error( | ||
`Handler not implemented for ${this.constructor.name}` | ||
); | ||
} | ||
|
||
// Metadata | ||
|
||
/** | ||
* Name of the category to sort this badge into (eg. "build"). Used to sort | ||
* the badges on the main shields.io website. | ||
*/ | ||
static get category() { | ||
return 'unknown'; | ||
} | ||
/** | ||
* Returns an object with two fields: | ||
* - format: Regular expression to use for URIs for this service's badges | ||
* - capture: Array of names for the capture groups in the regular | ||
* expression. The handler will be passed an object containing | ||
* the matches. | ||
*/ | ||
static get uri() { | ||
throw new Error(`URI not defined for ${this.name}`); | ||
} | ||
|
||
/** | ||
* Default data for the badge. Can include things such as default logo, color, | ||
* etc. These defaults will be used if the value is not explicitly overridden | ||
* by either the handler or by the user via URL parameters. | ||
*/ | ||
static get defaultBadgeData() { | ||
return {}; | ||
} | ||
|
||
/** | ||
* Example URIs for this service. These should use the format | ||
* specified in `uri`, and can be used to demonstrate how to use badges for | ||
* this service. | ||
*/ | ||
static getExamples() { | ||
return []; | ||
} | ||
|
||
static register(camp, handleRequest) { | ||
const serviceClass = this; // In a static context, "this" is the class. | ||
|
||
// Regular expressions treat "/" specially, so we need to escape them | ||
const escapedPath = serviceClass.uri.format.replace(/\//g, '\\/'); | ||
const fullRegex = '^' + escapedPath + '.(svg|png|gif|jpg|json)$'; | ||
|
||
camp.route(new RegExp(fullRegex), | ||
handleRequest(async (data, match, sendBadge, request) => { | ||
// Assumes the final capture group is the extension | ||
const format = match.pop(); | ||
const badgeData = getBadgeData( | ||
serviceClass.category, | ||
Object.assign({}, serviceClass.defaultBadgeData, data) | ||
); | ||
|
||
try { | ||
const namedParams = {}; | ||
if (serviceClass.uri.capture.length !== match.length - 1) { | ||
throw new Error( | ||
`Incorrect number of capture groups (expected `+ | ||
`${serviceClass.uri.capture.length}, got ${match.length - 1})` | ||
); | ||
} | ||
|
||
serviceClass.uri.capture.forEach((name, index) => { | ||
// The first capture group is the entire match, so every index is + 1 here | ||
namedParams[name] = match[index + 1]; | ||
}); | ||
|
||
const serviceInstance = new serviceClass({ | ||
sendAndCacheRequest: request.asPromise, | ||
}); | ||
const serviceData = await serviceInstance.handle(namedParams); | ||
const text = badgeData.text; | ||
if (serviceData.text) { | ||
text[1] = serviceData.text; | ||
} | ||
Object.assign(badgeData, serviceData); | ||
badgeData.text = text; | ||
sendBadge(format, badgeData); | ||
|
||
} catch (error) { | ||
console.log(error); | ||
const text = badgeData.text; | ||
text[1] = 'error'; | ||
badgeData.text = text; | ||
sendBadge(format, badgeData); | ||
} | ||
})); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
'use strict'; | ||
|
||
const assert = require('assert'); | ||
const sinon = require('sinon'); | ||
|
||
const BaseService = require('./base'); | ||
|
||
class DummyService extends BaseService { | ||
async handle({someArg}) { | ||
return { | ||
text: 'Hello ' + someArg, | ||
}; | ||
} | ||
|
||
static get category() { return 'cat'; } | ||
static get uri() { | ||
return { | ||
format: '/foo/([^/]+)', | ||
capture: ['someArg'] | ||
}; | ||
} | ||
} | ||
|
||
const expectedRouteRegex = /^\/foo\/([^/]+).(svg|png|gif|jpg|json)$/; | ||
|
||
describe('BaseService', () => { | ||
let mockCamp; | ||
let mockHandleRequest; | ||
|
||
beforeEach(() => { | ||
mockCamp = { | ||
route: sinon.spy(), | ||
}; | ||
mockHandleRequest = sinon.spy(); | ||
DummyService.register(mockCamp, mockHandleRequest); | ||
}); | ||
|
||
it('registers the service', () => { | ||
assert(mockCamp.route.calledOnce); | ||
assert.equal(mockCamp.route.getCall(0).args[0].toString(), expectedRouteRegex); | ||
}); | ||
|
||
it('handles the request', async () => { | ||
assert(mockHandleRequest.calledOnce); | ||
const requestHandler = mockHandleRequest.getCall(0).args[0]; | ||
|
||
const mockSendBadge = sinon.spy(); | ||
const mockRequest = { | ||
asPromise: sinon.spy(), | ||
}; | ||
await requestHandler( | ||
/*data*/ {}, | ||
/*match*/ '/foo/bar.svg'.match(expectedRouteRegex), | ||
mockSendBadge, | ||
mockRequest | ||
); | ||
|
||
assert(mockSendBadge.calledOnce); | ||
assert(mockSendBadge.calledWith( | ||
/*format*/ 'svg', | ||
{ | ||
text: ['cat', 'Hello bar'], | ||
colorscheme: 'lightgrey', | ||
template: 'default', | ||
logo: undefined, | ||
logoWidth: NaN, | ||
links: [], | ||
colorA: undefined, | ||
colorB: undefined, | ||
} | ||
)); | ||
}); | ||
}); |