Refactor [TeamCity] and add tests (#2601)
This commit is contained in:
committed by
Paul Melnikow
parent
a9839845a1
commit
3bbe2482bc
44
services/teamcity/teamcity-base.js
Normal file
44
services/teamcity/teamcity-base.js
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict'
|
||||
|
||||
const BaseJsonService = require('../base-json')
|
||||
const serverSecrets = require('../../lib/server-secrets')
|
||||
|
||||
module.exports = class TeamCityBase extends BaseJsonService {
|
||||
async fetch({
|
||||
protocol,
|
||||
hostAndPath,
|
||||
apiPath,
|
||||
schema,
|
||||
qs = {},
|
||||
errorMessages = {},
|
||||
}) {
|
||||
if (!hostAndPath) {
|
||||
// If hostAndPath is undefined then the user specified the legacy default path
|
||||
protocol = 'https'
|
||||
hostAndPath = 'teamcity.jetbrains.com'
|
||||
}
|
||||
const url = `${protocol}://${hostAndPath}/${apiPath}`
|
||||
const options = { qs }
|
||||
// JetBrains API Auth Docs: https://confluence.jetbrains.com/display/TCD18/REST+API#RESTAPI-RESTAuthentication
|
||||
if (serverSecrets && serverSecrets.teamcity_user) {
|
||||
options.auth = {
|
||||
user: serverSecrets.teamcity_user,
|
||||
pass: serverSecrets.teamcity_pass,
|
||||
}
|
||||
} else {
|
||||
qs.guest = 1
|
||||
}
|
||||
|
||||
const defaultErrorMessages = {
|
||||
404: 'build not found',
|
||||
}
|
||||
const errors = { ...defaultErrorMessages, ...errorMessages }
|
||||
|
||||
return this._requestJson({
|
||||
url,
|
||||
schema,
|
||||
options,
|
||||
errorMessages: errors,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,42 @@
|
||||
'use strict'
|
||||
|
||||
const LegacyService = require('../legacy-service')
|
||||
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
|
||||
const Joi = require('joi')
|
||||
const TeamCityBase = require('./teamcity-base')
|
||||
|
||||
// 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.
|
||||
function teamcityBadge(
|
||||
url,
|
||||
buildId,
|
||||
advanced,
|
||||
format,
|
||||
data,
|
||||
sendBadge,
|
||||
request
|
||||
) {
|
||||
const apiUrl = `${url}/app/rest/builds/buildType:(id:${buildId})?guest=1`
|
||||
const badgeData = getBadgeData('build', data)
|
||||
request(
|
||||
apiUrl,
|
||||
{ headers: { Accept: 'application/json' } },
|
||||
(err, res, buffer) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = 'inaccessible'
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
const buildStatusTextRegex = /^Success|Failure|Error|Tests( failed: \d+( \(\d+ new\))?)?(,)?( passed: \d+)?(,)?( ignored: \d+)?(,)?( muted: \d+)?$/
|
||||
const buildStatusSchema = Joi.object({
|
||||
status: Joi.equal('SUCCESS', 'FAILURE', 'ERROR').required(),
|
||||
statusText: Joi.string()
|
||||
.regex(buildStatusTextRegex)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
module.exports = class TeamCityBuild extends TeamCityBase {
|
||||
static render({ status, statusText, useVerbose }) {
|
||||
if (status === 'SUCCESS') {
|
||||
return {
|
||||
message: 'passing',
|
||||
color: 'brightgreen',
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(buffer)
|
||||
if (advanced)
|
||||
badgeData.text[1] = (
|
||||
data.statusText ||
|
||||
data.status ||
|
||||
''
|
||||
).toLowerCase()
|
||||
else badgeData.text[1] = (data.status || '').toLowerCase()
|
||||
if (data.status === 'SUCCESS') {
|
||||
badgeData.colorscheme = 'brightgreen'
|
||||
badgeData.text[1] = 'passing'
|
||||
} else {
|
||||
badgeData.colorscheme = 'red'
|
||||
}
|
||||
sendBadge(format, badgeData)
|
||||
} catch (e) {
|
||||
badgeData.text[1] = 'invalid'
|
||||
sendBadge(format, badgeData)
|
||||
} else if (statusText && useVerbose) {
|
||||
return {
|
||||
message: statusText.toLowerCase(),
|
||||
color: 'red',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: status.toLowerCase(),
|
||||
color: 'red',
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'build',
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = class TeamcityBuild extends LegacyService {
|
||||
static get category() {
|
||||
return 'build'
|
||||
}
|
||||
@@ -61,64 +44,69 @@ module.exports = class TeamcityBuild extends LegacyService {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'teamcity',
|
||||
format: '(?:codebetter|(http|https)/(.+)/(s|e))/([^/]+)',
|
||||
capture: ['protocol', 'hostAndPath', 'verbosity', 'buildId'],
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'TeamCity CodeBetter',
|
||||
previewUrl: 'codebetter/bt428',
|
||||
title: 'TeamCity Build Status (CodeBetter)',
|
||||
pattern: 'codebetter/:buildId',
|
||||
namedParams: {
|
||||
buildId: 'bt428',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
status: 'SUCCESS',
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'TeamCity (simple build status)',
|
||||
previewUrl: 'http/teamcity.jetbrains.com/s/bt345',
|
||||
title: 'TeamCity Simple Build Status',
|
||||
pattern: ':protocol/:hostAndPath/s/:buildId',
|
||||
namedParams: {
|
||||
protocol: 'https',
|
||||
hostAndPath: 'https/teamcity.jetbrains.com',
|
||||
buildId: 'bt428',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
status: 'SUCCESS',
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'TeamCity (full build status)',
|
||||
previewUrl: 'http/teamcity.jetbrains.com/e/bt345',
|
||||
title: 'TeamCity Full Build Status',
|
||||
pattern: ':protocol/:hostAndPath/e/:buildId',
|
||||
namedParams: {
|
||||
protocol: 'https',
|
||||
hostAndPath: 'https/teamcity.jetbrains.com',
|
||||
buildId: 'bt345',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
status: 'FAILURE',
|
||||
statusText: 'Tests failed: 4, passed: 1103, ignored: 2',
|
||||
useVerbose: true,
|
||||
}),
|
||||
keywords: ['test', 'test results'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler({ camp, cache }) {
|
||||
// Old url for CodeBetter TeamCity instance.
|
||||
camp.route(
|
||||
/^\/teamcity\/codebetter\/(.*)\.(svg|png|gif|jpg|json)$/,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const buildType = match[1] // eg, `bt428`.
|
||||
const format = match[2]
|
||||
teamcityBadge(
|
||||
'http://teamcity.codebetter.com',
|
||||
buildType,
|
||||
false,
|
||||
format,
|
||||
data,
|
||||
sendBadge,
|
||||
request
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
// Generic TeamCity instance
|
||||
camp.route(
|
||||
/^\/teamcity\/(http|https)\/(.*)\/(s|e)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const scheme = match[1]
|
||||
const serverUrl = match[2]
|
||||
const advanced = match[3] === 'e'
|
||||
const buildType = match[4] // eg, `bt428`.
|
||||
const format = match[5]
|
||||
teamcityBadge(
|
||||
`${scheme}://${serverUrl}`,
|
||||
buildType,
|
||||
advanced,
|
||||
format,
|
||||
data,
|
||||
sendBadge,
|
||||
request
|
||||
)
|
||||
})
|
||||
)
|
||||
async handle({ protocol, hostAndPath, verbosity, buildId }) {
|
||||
// JetBrains Docs: https://confluence.jetbrains.com/display/TCD18/REST+API#RESTAPI-BuildStatusIcon
|
||||
const buildLocator = `buildType:(id:${buildId})`
|
||||
const apiPath = `app/rest/builds/${encodeURIComponent(buildLocator)}`
|
||||
const json = await this.fetch({
|
||||
protocol,
|
||||
hostAndPath,
|
||||
apiPath,
|
||||
schema: buildStatusSchema,
|
||||
})
|
||||
// If the verbosity is 'e' then the user has requested the verbose (full) build status.
|
||||
const useVerbose = verbosity === 'e'
|
||||
return this.constructor.render({
|
||||
status: json.status,
|
||||
statusText: json.statusText,
|
||||
useVerbose,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
177
services/teamcity/teamcity-build.tester.js
Normal file
177
services/teamcity/teamcity-build.tester.js
Normal file
@@ -0,0 +1,177 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
|
||||
const { colorScheme } = require('../test-helpers')
|
||||
const { withRegex } = require('../test-validators')
|
||||
const {
|
||||
mockTeamCityCreds,
|
||||
pass,
|
||||
user,
|
||||
restore,
|
||||
} = require('./teamcity-test-helpers')
|
||||
const t = (module.exports = require('../create-service-tester')())
|
||||
|
||||
const buildStatusValues = Joi.equal('passing', 'failure', 'error').required()
|
||||
const buildStatusTextRegex = /^success|failure|error|tests( failed: \d+( \(\d+ new\))?)?(,)?( passed: \d+)?(,)?( ignored: \d+)?(,)?( muted: \d+)?$/
|
||||
|
||||
t.create('live: codebetter unknown build')
|
||||
.get('/codebetter/btabc.json')
|
||||
.expectJSON({ name: 'build', value: 'build not found' })
|
||||
|
||||
t.create('live: codebetter known build')
|
||||
.get('/codebetter/bt428.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'build',
|
||||
value: buildStatusValues,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('live: simple status for known build')
|
||||
.get('/https/teamcity.jetbrains.com/s/bt345.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'build',
|
||||
value: buildStatusValues,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('live: full status for known build')
|
||||
.get('/https/teamcity.jetbrains.com/e/bt345.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'build',
|
||||
value: withRegex(buildStatusTextRegex),
|
||||
})
|
||||
)
|
||||
|
||||
t.create('codebetter success build')
|
||||
.get('/codebetter/bt123.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://teamcity.jetbrains.com/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt123)')}`)
|
||||
.query({ guest: 1 })
|
||||
.reply(200, {
|
||||
status: 'SUCCESS',
|
||||
statusText: 'Success',
|
||||
})
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'build',
|
||||
value: 'passing',
|
||||
colorB: colorScheme.brightgreen,
|
||||
})
|
||||
|
||||
t.create('codebetter failure build')
|
||||
.get('/codebetter/bt123.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://teamcity.jetbrains.com/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt123)')}`)
|
||||
.query({ guest: 1 })
|
||||
.reply(200, {
|
||||
status: 'FAILURE',
|
||||
statusText: 'Tests failed: 2',
|
||||
})
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'build',
|
||||
value: 'failure',
|
||||
colorB: colorScheme.red,
|
||||
})
|
||||
|
||||
t.create('simple build status with passed build')
|
||||
.get('/https/myteamcity.com:8080/s/bt321.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://myteamcity.com:8080/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt321)')}`)
|
||||
.query({ guest: 1 })
|
||||
.reply(200, {
|
||||
status: 'SUCCESS',
|
||||
statusText: 'Tests passed: 100',
|
||||
})
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'build',
|
||||
value: 'passing',
|
||||
colorB: colorScheme.brightgreen,
|
||||
})
|
||||
|
||||
t.create('simple build status with failed build')
|
||||
.get('/https/myteamcity.com:8080/s/bt999.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://myteamcity.com:8080/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt999)')}`)
|
||||
.query({ guest: 1 })
|
||||
.reply(200, {
|
||||
status: 'FAILURE',
|
||||
statusText: 'Tests failed: 10 (2 new)',
|
||||
})
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'build',
|
||||
value: 'failure',
|
||||
colorB: colorScheme.red,
|
||||
})
|
||||
|
||||
t.create('full build status with passed build')
|
||||
.get('/https/selfhosted.teamcity.com:4000/e/bt321.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://selfhosted.teamcity.com:4000/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt321)')}`)
|
||||
.query({ guest: 1 })
|
||||
.reply(200, {
|
||||
status: 'SUCCESS',
|
||||
statusText: 'Tests passed: 100, ignored: 3',
|
||||
})
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'build',
|
||||
value: 'passing',
|
||||
colorB: colorScheme.brightgreen,
|
||||
})
|
||||
|
||||
t.create('full build status with failed build')
|
||||
.get(
|
||||
'/https/selfhosted.teamcity.com:4000/tc/e/bt567.json?style=_shields_test'
|
||||
)
|
||||
.intercept(nock =>
|
||||
nock('https://selfhosted.teamcity.com:4000/tc/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt567)')}`)
|
||||
.query({ guest: 1 })
|
||||
.reply(200, {
|
||||
status: 'FAILURE',
|
||||
statusText: 'Tests failed: 10 (2 new), passed: 99',
|
||||
})
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'build',
|
||||
value: 'tests failed: 10 (2 new), passed: 99',
|
||||
colorB: colorScheme.red,
|
||||
})
|
||||
|
||||
t.create('with auth')
|
||||
.before(mockTeamCityCreds)
|
||||
.get('/https/selfhosted.teamcity.com/e/bt678.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://selfhosted.teamcity.com/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt678)')}`)
|
||||
.query({})
|
||||
// This ensures that the expected credentials from serverSecrets are actually being sent with the HTTP request.
|
||||
// Without this the request wouldn't match and the test would fail.
|
||||
.basicAuth({
|
||||
user,
|
||||
pass,
|
||||
})
|
||||
.reply(200, {
|
||||
status: 'FAILURE',
|
||||
statusText:
|
||||
'Tests failed: 1 (1 new), passed: 50246, ignored: 1, muted: 12',
|
||||
})
|
||||
)
|
||||
.finally(restore)
|
||||
.expectJSON({
|
||||
name: 'build',
|
||||
value: 'tests failed: 1 (1 new), passed: 50246, ignored: 1, muted: 12',
|
||||
colorB: colorScheme.red,
|
||||
})
|
||||
@@ -1,92 +1,107 @@
|
||||
'use strict'
|
||||
|
||||
const LegacyService = require('../legacy-service')
|
||||
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
|
||||
const {
|
||||
coveragePercentage: coveragePercentageColor,
|
||||
} = require('../../lib/color-formatters')
|
||||
const Joi = require('joi')
|
||||
const { InvalidResponse } = require('../errors')
|
||||
const TeamCityBase = require('./teamcity-base')
|
||||
const { coveragePercentage } = require('../../lib/color-formatters')
|
||||
|
||||
// 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 TeamcityCoverage extends LegacyService {
|
||||
static get category() {
|
||||
return 'quality'
|
||||
}
|
||||
const buildStatisticsSchema = Joi.object({
|
||||
property: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
name: Joi.string().required(),
|
||||
value: Joi.string().required(),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
static get route() {
|
||||
module.exports = class TeamCityCoverage extends TeamCityBase {
|
||||
static render({ coverage }) {
|
||||
return {
|
||||
base: 'teamcity/coverage',
|
||||
pattern: ':buildType',
|
||||
message: `${coverage.toFixed(0)}%`,
|
||||
color: coveragePercentage(coverage),
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'TeamCity CodeBetter Coverage',
|
||||
namedParams: { buildType: 'bt428' },
|
||||
staticPreview: { message: '55%', color: 'yellow' },
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'coverage',
|
||||
}
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler({ camp, cache }) {
|
||||
camp.route(
|
||||
/^\/teamcity\/coverage\/(.*)\.(svg|png|gif|jpg|json)$/,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const buildType = match[1] // eg, `bt428`.
|
||||
const format = match[2]
|
||||
const apiUrl = `http://teamcity.codebetter.com/app/rest/builds/buildType:(id:${buildType})/statistics?guest=1`
|
||||
const badgeData = getBadgeData('coverage', data)
|
||||
request(
|
||||
apiUrl,
|
||||
{ headers: { Accept: 'application/json' } },
|
||||
(err, res, buffer) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = 'inaccessible'
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(buffer)
|
||||
let covered
|
||||
let total
|
||||
static get category() {
|
||||
return 'quality'
|
||||
}
|
||||
|
||||
data.property.forEach(property => {
|
||||
if (property.name === 'CodeCoverageAbsSCovered') {
|
||||
covered = property.value
|
||||
} else if (property.name === 'CodeCoverageAbsSTotal') {
|
||||
total = property.value
|
||||
}
|
||||
})
|
||||
static get route() {
|
||||
return {
|
||||
base: 'teamcity/coverage',
|
||||
format: '(?:(http|https)/(.+)/)?([^/]+)',
|
||||
capture: ['protocol', 'hostAndPath', 'buildId'],
|
||||
}
|
||||
}
|
||||
|
||||
if (covered === undefined || total === undefined) {
|
||||
badgeData.text[1] = 'malformed'
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'TeamCity Coverage (CodeBetter)',
|
||||
pattern: ':buildId',
|
||||
namedParams: {
|
||||
buildId: 'bt428',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
coverage: 82,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'TeamCity Coverage',
|
||||
pattern: ':protocol/:hostAndPath/s/:buildId',
|
||||
namedParams: {
|
||||
protocol: 'https',
|
||||
hostAndPath: 'https/teamcity.jetbrains.com',
|
||||
buildId: 'bt428',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
coverage: 95,
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const percentage = (covered / total) * 100
|
||||
badgeData.text[1] = `${percentage.toFixed(0)}%`
|
||||
badgeData.colorscheme = coveragePercentageColor(percentage)
|
||||
sendBadge(format, badgeData)
|
||||
} catch (e) {
|
||||
badgeData.text[1] = 'invalid'
|
||||
sendBadge(format, badgeData)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
async handle({ protocol, hostAndPath, buildId }) {
|
||||
// JetBrains Docs: https://confluence.jetbrains.com/display/TCD18/REST+API#RESTAPI-Statistics
|
||||
const buildLocator = `buildType:(id:${buildId})`
|
||||
const apiPath = `app/rest/builds/${encodeURIComponent(
|
||||
buildLocator
|
||||
)}/statistics`
|
||||
const data = await this.fetch({
|
||||
protocol,
|
||||
hostAndPath,
|
||||
apiPath,
|
||||
schema: buildStatisticsSchema,
|
||||
})
|
||||
|
||||
const { coverage } = this.transform({ data })
|
||||
return this.constructor.render({ coverage })
|
||||
}
|
||||
|
||||
transform({ data }) {
|
||||
let covered, total
|
||||
|
||||
for (const p of data.property) {
|
||||
if (p.name === 'CodeCoverageAbsSCovered') {
|
||||
covered = +p.value
|
||||
} else if (p.name === 'CodeCoverageAbsSTotal') {
|
||||
total = +p.value
|
||||
}
|
||||
|
||||
if (covered !== undefined && total !== undefined) {
|
||||
const coverage = covered ? (covered / total) * 100 : 0
|
||||
return { coverage }
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidResponse({ prettyMessage: 'no coverage data available' })
|
||||
}
|
||||
}
|
||||
|
||||
117
services/teamcity/teamcity-coverage.tester.js
Normal file
117
services/teamcity/teamcity-coverage.tester.js
Normal file
@@ -0,0 +1,117 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
|
||||
const { colorScheme } = require('../test-helpers')
|
||||
const { isIntegerPercentage } = require('../test-validators')
|
||||
const {
|
||||
mockTeamCityCreds,
|
||||
pass,
|
||||
user,
|
||||
restore,
|
||||
} = require('./teamcity-test-helpers')
|
||||
const t = (module.exports = require('../create-service-tester')())
|
||||
|
||||
t.create('live: valid buildId')
|
||||
.get('/bt428.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'coverage',
|
||||
value: isIntegerPercentage,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('live: specified instance valid buildId')
|
||||
.get('/https/teamcity.jetbrains.com/bt428.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'coverage',
|
||||
value: isIntegerPercentage,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('live: invalid buildId')
|
||||
.get('/btABC999.json')
|
||||
.expectJSON({ name: 'coverage', value: 'build not found' })
|
||||
|
||||
t.create('live: specified instance invalid buildId')
|
||||
.get('/https/teamcity.jetbrains.com/btABC000.json')
|
||||
.expectJSON({ name: 'coverage', value: 'build not found' })
|
||||
|
||||
t.create('404 latest build error response')
|
||||
.get('/bt123.json')
|
||||
.intercept(nock =>
|
||||
nock('https://teamcity.jetbrains.com/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt123)')}/statistics`)
|
||||
.query({ guest: 1 })
|
||||
.reply(404)
|
||||
)
|
||||
.expectJSON({ name: 'coverage', value: 'build not found' })
|
||||
|
||||
t.create('no coverage data for build')
|
||||
.get('/bt234.json')
|
||||
.intercept(nock =>
|
||||
nock('https://teamcity.jetbrains.com/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt234)')}/statistics`)
|
||||
.query({ guest: 1 })
|
||||
.reply(200, { property: [] })
|
||||
)
|
||||
.expectJSON({ name: 'coverage', value: 'no coverage data available' })
|
||||
|
||||
t.create('zero lines covered')
|
||||
.get('/bt345.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://teamcity.jetbrains.com/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt345)')}/statistics`)
|
||||
.query({ guest: 1 })
|
||||
.reply(200, {
|
||||
property: [
|
||||
{
|
||||
name: 'CodeCoverageAbsSCovered',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
name: 'CodeCoverageAbsSTotal',
|
||||
value: '345',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.expectJSON({
|
||||
name: 'coverage',
|
||||
value: '0%',
|
||||
colorB: colorScheme.red,
|
||||
})
|
||||
|
||||
t.create('with auth, lines covered')
|
||||
.before(mockTeamCityCreds)
|
||||
.get('/https/selfhosted.teamcity.com/bt678.json?style=_shields_test')
|
||||
.intercept(nock =>
|
||||
nock('https://selfhosted.teamcity.com/app/rest/builds')
|
||||
.get(`/${encodeURIComponent('buildType:(id:bt678)')}/statistics`)
|
||||
.query({})
|
||||
// This ensures that the expected credentials from serverSecrets are actually being sent with the HTTP request.
|
||||
// Without this the request wouldn't match and the test would fail.
|
||||
.basicAuth({
|
||||
user,
|
||||
pass,
|
||||
})
|
||||
.reply(200, {
|
||||
property: [
|
||||
{
|
||||
name: 'CodeCoverageAbsSCovered',
|
||||
value: '82',
|
||||
},
|
||||
{
|
||||
name: 'CodeCoverageAbsSTotal',
|
||||
value: '100',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.finally(restore)
|
||||
.expectJSON({
|
||||
name: 'coverage',
|
||||
value: '82%',
|
||||
colorB: colorScheme.yellowgreen,
|
||||
})
|
||||
25
services/teamcity/teamcity-test-helpers.js
Normal file
25
services/teamcity/teamcity-test-helpers.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict'
|
||||
|
||||
const sinon = require('sinon')
|
||||
const serverSecrets = require('../../lib/server-secrets')
|
||||
|
||||
const user = 'admin'
|
||||
const pass = 'password'
|
||||
|
||||
function mockTeamCityCreds() {
|
||||
serverSecrets['teamcity_user'] = undefined
|
||||
serverSecrets['teamcity_pass'] = undefined
|
||||
sinon.stub(serverSecrets, 'teamcity_user').value(user)
|
||||
sinon.stub(serverSecrets, 'teamcity_pass').value(pass)
|
||||
}
|
||||
|
||||
function restore() {
|
||||
sinon.restore()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
user,
|
||||
pass,
|
||||
mockTeamCityCreds,
|
||||
restore,
|
||||
}
|
||||
Reference in New Issue
Block a user