Add SymfonyInsight stars badge, run [SymfonyInsight sensiolabs amo chrome-web-store redmine spigetratings vaadin-directory visualstudiomarketplacerating wordpress] (#2971)

* feat: added stars badge for symfony insight

* refactor: changed symfony star determination logic

* feat: updating symfony to handle old scan scenarios

* feat: updated symfony insight to handle older projects

* tests: removed another test for symfony insight per request
This commit is contained in:
Caleb Cartwright
2019-02-20 17:15:31 -06:00
committed by GitHub
parent 96d48dc486
commit a4bd3f5fd6
14 changed files with 827 additions and 608 deletions

View File

@@ -7,7 +7,7 @@
const moment = require('moment')
moment().format()
function starRating(rating) {
function starRating(rating, max = 5) {
const flooredRating = Math.floor(rating)
let stars = ''
while (stars.length < flooredRating) {
@@ -23,7 +23,8 @@ function starRating(rating) {
} else if (decimal >= 0.125) {
stars += '¼'
}
while (stars.length < 5) {
while (stars.length < max) {
stars += '☆'
}
return stars

View File

@@ -23,6 +23,7 @@ describe('Text formatters', function() {
given(2.566).expect('★★½☆☆')
given(2.2).expect('★★¼☆☆')
given(3).expect('★★★☆☆')
given(2, 4).expect('★★☆☆')
})
test(currencyFromCode, () => {

View File

@@ -0,0 +1,17 @@
'use strict'
const { redirector } = require('..')
module.exports = [
// The SymfonyInsight service was previously branded as SensioLabs, and
// accordingly the badge path used to be /sensiolabs/i/projectUuid'.
redirector({
category: 'analysis',
route: {
base: 'sensiolabs/i',
pattern: ':projectUuid',
},
target: ({ projectUuid }) => `/symfony/i/grade/${projectUuid}`,
dateAdded: new Date('2019-02-08'),
}),
]

View File

@@ -0,0 +1,18 @@
'use strict'
const { ServiceTester } = require('../tester')
const t = (module.exports = new ServiceTester({
id: 'sensiolabs',
title: 'SensioLabs',
}))
t.create('sensiolabs insight')
.get('/i/45afb680-d4e6-4e66-93ea-bcfa79eb8a87.svg', {
followRedirect: false,
})
.expectStatus(301)
.expectHeader(
'Location',
'/symfony/i/grade/45afb680-d4e6-4e66-93ea-bcfa79eb8a87.svg'
)

View File

@@ -0,0 +1,146 @@
'use strict'
const Joi = require('joi')
const serverSecrets = require('../../lib/server-secrets')
const { BaseXmlService, Inaccessible } = require('..')
const violationSchema = Joi.object({
severity: Joi.equal('info', 'minor', 'major', 'critical').required(),
}).required()
const schema = Joi.object({
project: Joi.object({
'last-analysis': Joi.object({
status: Joi.equal(
'ordered',
'running',
'measured',
'analyzed',
'finished'
)
.allow('')
.required(),
grade: Joi.equal('platinum', 'gold', 'silver', 'bronze', 'none'),
violations: Joi.object({
// RE: https://github.com/NaturalIntelligence/fast-xml-parser/issues/68
// The BaseXmlService uses the fast-xml-parser which doesn't support forcing
// the xml nodes to always be parsed as an array. Currently, if the response
// only contains a single violation then it will be parsed as an object,
// otherwise it will be parsed as an array.
violation: Joi.array()
.items(violationSchema)
.single()
.required(),
}),
}),
}).required(),
}).required()
const keywords = ['sensiolabs', 'sensio']
const gradeColors = {
none: 'red',
bronze: '#C88F6A',
silver: '#C0C0C0',
gold: '#EBC760',
platinum: '#E5E4E2',
}
class SymfonyInsightBase extends BaseXmlService {
static get defaultBadgeData() {
return {
label: 'symfony insight',
}
}
static get category() {
return 'analysis'
}
async fetch({ projectUuid }) {
const url = `https://insight.symfony.com/api/projects/${projectUuid}`
const options = {
headers: {
Accept: 'application/vnd.com.sensiolabs.insight+xml',
},
}
if (
!serverSecrets.sl_insight_userUuid ||
!serverSecrets.sl_insight_apiToken
) {
throw new Inaccessible({
prettyMessage: 'required API tokens not found in config',
})
}
options.auth = {
user: serverSecrets.sl_insight_userUuid,
pass: serverSecrets.sl_insight_apiToken,
}
return this._requestXml({
url,
options,
schema,
errorMessages: {
401: 'not authorized to access project',
404: 'project not found',
},
parserOptions: {
attributeNamePrefix: '',
ignoreAttributes: false,
},
})
}
transform({ data }) {
const lastAnalysis = data.project['last-analysis']
let numViolations = 0
let numCriticalViolations = 0
let numMajorViolations = 0
let numMinorViolations = 0
let numInfoViolations = 0
const violationContainer = lastAnalysis.violations
if (violationContainer && violationContainer.violation) {
let violations = []
// See above note on schema RE: https://github.com/NaturalIntelligence/fast-xml-parser/issues/68
// This covers the scenario of multiple violations which are parsed as an array and single
// violations which is parsed as a single object.
if (Array.isArray(violationContainer.violation)) {
violations = violationContainer.violation
} else {
violations.push(violationContainer.violation)
}
numViolations = violations.length
violations.forEach(violation => {
if (violation.severity === 'critical') {
numCriticalViolations++
} else if (violation.severity === 'major') {
numMajorViolations++
} else if (violation.severity === 'minor') {
numMinorViolations++
} else {
numInfoViolations++
}
})
}
return {
status: lastAnalysis.status,
grade: lastAnalysis.grade,
numViolations,
numCriticalViolations,
numMajorViolations,
numMinorViolations,
numInfoViolations,
}
}
}
module.exports = {
SymfonyInsightBase,
keywords,
gradeColors,
}

View File

@@ -0,0 +1,57 @@
'use strict'
const {
SymfonyInsightBase,
keywords,
gradeColors,
} = require('./symfony-insight-base')
module.exports = class SymfonyInsightGrade extends SymfonyInsightBase {
static render({ status, grade }) {
const label = 'grade'
if (status !== 'finished' && status !== '') {
return {
label,
message: 'pending',
color: 'lightgrey',
}
}
const message = grade === 'none' ? 'no medal' : grade
return {
label,
message,
color: gradeColors[grade],
}
}
static get route() {
return {
base: 'symfony/i/grade',
pattern: ':projectUuid',
}
}
static get examples() {
return [
{
title: 'SymfonyInsight Grade',
namedParams: {
projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87',
},
staticPreview: this.render({
grade: 'bronze',
status: 'finished',
}),
keywords,
},
]
}
async handle({ projectUuid }) {
const data = await this.fetch({ projectUuid })
const { grade, status } = this.transform({ data })
return this.constructor.render({ grade, status })
}
}

View File

@@ -0,0 +1,152 @@
'use strict'
const Joi = require('joi')
const t = (module.exports = require('../tester').createServiceTester())
const {
createTest,
runningMockResponse,
platinumMockResponse,
goldMockResponse,
silverMockResponse,
bronzeMockResponse,
noMedalMockResponse,
prepLiveTest,
sampleProjectUuid,
realTokenExists,
setSymfonyInsightCredsToFalsy,
restore,
} = require('./symfony-test-helpers')
createTest(t, 'live: valid project grade', { withMockCreds: false })
.before(prepLiveTest)
.get(`/${sampleProjectUuid}.json`)
.timeout(15000)
.interceptIf(!realTokenExists, nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, platinumMockResponse)
)
.expectJSONTypes(
Joi.object().keys({
name: 'grade',
value: Joi.equal(
'platinum',
'gold',
'silver',
'bronze',
'no medal'
).required(),
})
)
createTest(t, 'live: nonexistent project', { withMockCreds: false })
.before(prepLiveTest)
.get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88.json')
.interceptIf(!realTokenExists, nock =>
nock('https://insight.symfony.com/api/projects')
.get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88')
.reply(404)
)
.expectJSON({
name: 'symfony insight',
value: 'project not found',
})
createTest(t, '401 not authorized grade')
.get(`/${sampleProjectUuid}.json`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(401)
)
.expectJSON({
name: 'symfony insight',
value: 'not authorized to access project',
})
createTest(t, 'pending project grade')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, runningMockResponse)
)
.expectJSON({
name: 'grade',
value: 'pending',
color: 'lightgrey',
})
createTest(t, 'platinum grade')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, platinumMockResponse)
)
.expectJSON({
name: 'grade',
value: 'platinum',
color: '#e5e4e2',
})
createTest(t, 'gold grade')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, goldMockResponse)
)
.expectJSON({
name: 'grade',
value: 'gold',
color: '#ebc760',
})
createTest(t, 'silver grade')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, silverMockResponse)
)
.expectJSON({
name: 'grade',
value: 'silver',
color: '#c0c0c0',
})
createTest(t, 'bronze grade')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, bronzeMockResponse)
)
.expectJSON({
name: 'grade',
value: 'bronze',
color: '#c88f6a',
})
createTest(t, 'no medal grade')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, noMedalMockResponse)
)
.expectJSON({
name: 'grade',
value: 'no medal',
color: 'red',
})
createTest(t, 'auth missing', { withMockCreds: false })
.before(setSymfonyInsightCredsToFalsy)
.get(`/${sampleProjectUuid}.json`)
.expectJSON({
name: 'symfony insight',
value: 'required API tokens not found in config',
})
.after(restore)

View File

@@ -0,0 +1,64 @@
'use strict'
const { starRating } = require('../../lib/text-formatters')
const {
SymfonyInsightBase,
keywords,
gradeColors,
} = require('./symfony-insight-base')
const gradeStars = {
none: 0,
bronze: 1,
silver: 2,
gold: 3,
platinum: 4,
}
module.exports = class SymfonyInsightStars extends SymfonyInsightBase {
static render({ status, grade }) {
const label = 'stars'
if (status !== 'finished' && status !== '') {
return {
label,
message: 'pending',
color: 'lightgrey',
}
}
const numStars = gradeStars[grade]
return {
label,
message: starRating(numStars, 4),
color: gradeColors[grade],
}
}
static get route() {
return {
base: 'symfony/i/stars',
pattern: ':projectUuid',
}
}
static get examples() {
return [
{
title: 'SymfonyInsight Stars',
namedParams: {
projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87',
},
staticPreview: this.render({
grade: 'silver',
status: 'finished',
}),
keywords,
},
]
}
async handle({ projectUuid }) {
const data = await this.fetch({ projectUuid })
const { grade, status } = this.transform({ data })
return this.constructor.render({ grade, status })
}
}

View File

@@ -0,0 +1,126 @@
'use strict'
const Joi = require('joi')
const t = (module.exports = require('../tester').createServiceTester())
const { withRegex } = require('../test-validators')
const {
createTest,
runningMockResponse,
platinumMockResponse,
goldMockResponse,
silverMockResponse,
bronzeMockResponse,
noMedalMockResponse,
prepLiveTest,
sampleProjectUuid,
realTokenExists,
} = require('./symfony-test-helpers')
createTest(t, 'live: valid project stars', { withMockCreds: false })
.before(prepLiveTest)
.get(`/${sampleProjectUuid}.json`)
.timeout(15000)
.interceptIf(!realTokenExists, nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, platinumMockResponse)
)
.expectJSONTypes(
Joi.object().keys({
name: 'stars',
value: withRegex(
/^(?=.{4}$)(\u2605{0,4}[\u00BC\u00BD\u00BE]?\u2606{0,4})$/
),
})
)
createTest(t, 'live (stars): nonexistent project', { withMockCreds: false })
.before(prepLiveTest)
.get('/abc.json')
.interceptIf(!realTokenExists, nock =>
nock('https://insight.symfony.com/api/projects')
.get('/abc')
.reply(404)
)
.expectJSON({
name: 'symfony insight',
value: 'project not found',
})
createTest(t, 'pending project stars')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, runningMockResponse)
)
.expectJSON({
name: 'stars',
value: 'pending',
color: 'lightgrey',
})
createTest(t, 'platinum stars')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, platinumMockResponse)
)
.expectJSON({
name: 'stars',
value: '★★★★',
color: '#e5e4e2',
})
createTest(t, 'gold stars')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, goldMockResponse)
)
.expectJSON({
name: 'stars',
value: '★★★☆',
color: '#ebc760',
})
createTest(t, 'silver stars')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, silverMockResponse)
)
.expectJSON({
name: 'stars',
value: '★★☆☆',
color: '#c0c0c0',
})
createTest(t, 'bronze stars')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, bronzeMockResponse)
)
.expectJSON({
name: 'stars',
value: '★☆☆☆',
color: '#c88f6a',
})
createTest(t, 'no medal stars')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, noMedalMockResponse)
)
.expectJSON({
name: 'stars',
value: '☆☆☆☆',
color: 'red',
})

View File

@@ -0,0 +1,85 @@
'use strict'
const { SymfonyInsightBase, keywords } = require('./symfony-insight-base')
module.exports = class SymfonyInsightViolations extends SymfonyInsightBase {
static render({
status,
numViolations,
numCriticalViolations,
numMajorViolations,
numMinorViolations,
numInfoViolations,
}) {
const label = 'violations'
if (status !== 'finished' && status !== '') {
return {
label,
message: 'pending',
color: 'lightgrey',
}
}
if (numViolations === 0) {
return {
label,
message: '0',
color: 'brightgreen',
}
}
let color = 'yellowgreen'
const violationSummary = []
if (numInfoViolations > 0) {
violationSummary.push(`${numInfoViolations} info`)
}
if (numMinorViolations > 0) {
violationSummary.unshift(`${numMinorViolations} minor`)
color = 'yellow'
}
if (numMajorViolations > 0) {
violationSummary.unshift(`${numMajorViolations} major`)
color = 'orange'
}
if (numCriticalViolations > 0) {
violationSummary.unshift(`${numCriticalViolations} critical`)
color = 'red'
}
return {
label,
message: violationSummary.join(', '),
color,
}
}
static get route() {
return {
base: 'symfony/i/violations',
pattern: ':projectUuid',
}
}
static get examples() {
return [
{
title: 'SymfonyInsight Violations',
namedParams: {
projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87',
},
staticPreview: this.render({
numViolations: 0,
status: 'finished',
}),
keywords,
},
]
}
async handle({ projectUuid }) {
const data = await this.fetch({ projectUuid })
const lastAnalysis = this.transform({ data })
return this.constructor.render(lastAnalysis)
}
}

View File

@@ -0,0 +1,141 @@
'use strict'
const Joi = require('joi')
const t = (module.exports = require('../tester').createServiceTester())
const { withRegex } = require('../test-validators')
const {
createTest,
goldMockResponse,
runningMockResponse,
prepLiveTest,
sampleProjectUuid,
realTokenExists,
mockSymfonyUser,
mockSymfonyToken,
criticalViolation,
majorViolation,
minorViolation,
infoViolation,
multipleViolations,
} = require('./symfony-test-helpers')
createTest(t, 'live: valid project violations', { withMockCreds: false })
.before(prepLiveTest)
.get(`/${sampleProjectUuid}.json`)
.timeout(15000)
.interceptIf(!realTokenExists, nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, multipleViolations)
)
.expectJSONTypes(
Joi.object().keys({
name: 'violations',
value: withRegex(
/\d* critical|\d* critical, \d* major|\d* critical, \d* major, \d* minor|\d* critical, \d* major, \d* minor, \d* info|\d* critical, \d* minor|\d* critical, \d* info|\d* major|\d* major, \d* minor|\d* major, \d* minor, \d* info|\d* major, \d* info|\d* minor|\d* minor, \d* info/
),
})
)
createTest(t, 'pending project grade')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, runningMockResponse)
)
.expectJSON({
name: 'violations',
value: 'pending',
color: 'lightgrey',
})
createTest(t, 'zero violations')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, goldMockResponse)
)
.expectJSON({
name: 'violations',
value: '0',
color: 'brightgreen',
})
createTest(t, 'critical violations')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, criticalViolation)
)
.expectJSON({
name: 'violations',
value: '1 critical',
color: 'red',
})
createTest(t, 'major violations')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, majorViolation)
)
.expectJSON({
name: 'violations',
value: '1 major',
color: 'orange',
})
createTest(t, 'minor violations')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.basicAuth({
user: mockSymfonyUser,
pass: mockSymfonyToken,
})
.reply(200, minorViolation)
)
.expectJSON({
name: 'violations',
value: '1 minor',
color: 'yellow',
})
createTest(t, 'info violations')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.basicAuth({
user: mockSymfonyUser,
pass: mockSymfonyToken,
})
.reply(200, infoViolation)
)
.expectJSON({
name: 'violations',
value: '1 info',
color: 'yellowgreen',
})
createTest(t, 'multiple violations grade')
.get(`/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.basicAuth({
user: mockSymfonyUser,
pass: mockSymfonyToken,
})
.reply(200, multipleViolations)
)
.expectJSON({
name: 'violations',
value: '1 critical, 1 info',
color: 'red',
})

View File

@@ -1,288 +0,0 @@
'use strict'
const Joi = require('joi')
const serverSecrets = require('../../lib/server-secrets')
const { BaseXmlService, Inaccessible } = require('..')
const violationSchema = Joi.object({
severity: Joi.equal('info', 'minor', 'major', 'critical').required(),
}).required()
const schema = Joi.object({
project: Joi.object({
'last-analysis': Joi.object({
status: Joi.equal(
'ordered',
'running',
'measured',
'analyzed',
'finished'
).required(),
grade: Joi.equal('platinum', 'gold', 'silver', 'bronze', 'none'),
violations: Joi.object({
// RE: https://github.com/NaturalIntelligence/fast-xml-parser/issues/68
// The BaseXmlService uses the fast-xml-parser which doesn't support forcing
// the xml nodes to always be parsed as an array. Currently, if the response
// only contains a single violation then it will be parsed as an object,
// otherwise it will be parsed as an array.
violation: Joi.array()
.items(violationSchema)
.single()
.required(),
}),
}),
}).required(),
}).required()
const keywords = ['sensiolabs']
module.exports = class SymfonyInsight extends BaseXmlService {
static render({
metric,
status,
grade,
numViolations,
numCriticalViolations,
numMajorViolations,
numMinorViolations,
numInfoViolations,
}) {
if (status !== 'finished') {
return {
label: metric,
message: 'pending',
color: 'lightgrey',
}
}
if (metric === 'grade') {
return this.renderGradeBadge({ grade })
} else {
return this.renderViolationsBadge({
numViolations,
numCriticalViolations,
numMajorViolations,
numMinorViolations,
numInfoViolations,
})
}
}
static renderGradeBadge({ grade }) {
let color,
message = grade
if (grade === 'platinum') {
color = '#E5E4E2'
} else if (grade === 'gold') {
color = '#EBC760'
} else if (grade === 'silver') {
color = '#C0C0C0'
} else if (grade === 'bronze') {
color = '#C88F6A'
} else {
message = 'no medal'
color = 'red'
}
return {
label: 'grade',
message,
color,
}
}
static renderViolationsBadge({
numViolations,
numCriticalViolations,
numMajorViolations,
numMinorViolations,
numInfoViolations,
}) {
if (numViolations === 0) {
return {
label: 'violations',
message: '0',
color: 'brightgreen',
}
}
let color = 'yellowgreen'
const violationSummary = []
if (numInfoViolations > 0) {
violationSummary.push(`${numInfoViolations} info`)
}
if (numMinorViolations > 0) {
violationSummary.unshift(`${numMinorViolations} minor`)
color = 'yellow'
}
if (numMajorViolations > 0) {
violationSummary.unshift(`${numMajorViolations} major`)
color = 'orange'
}
if (numCriticalViolations > 0) {
violationSummary.unshift(`${numCriticalViolations} critical`)
color = 'red'
}
return {
label: 'violations',
message: violationSummary.join(', '),
color,
}
}
static get defaultBadgeData() {
return {
label: 'symfony insight',
}
}
static get category() {
return 'analysis'
}
static get route() {
return {
base: '',
// The SymfonyInsight service was previously branded as SensioLabs, and
// accordingly the badge path used to be /sensiolabs/i/projectUuid'.
// This is used to provide backward compatibility for the old path as well as
// supporting the new/current path.
format: '(?:sensiolabs/i|symfony/i/(grade|violations))/([^/]+)',
capture: ['metric', 'projectUuid'],
}
}
static get examples() {
return [
{
title: 'SymfonyInsight Grade',
pattern: 'symfony/i/grade/:projectUuid',
namedParams: {
projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87',
},
staticPreview: this.renderGradeBadge({
grade: 'bronze',
}),
keywords,
},
{
title: 'SymfonyInsight Violations',
pattern: 'symfony/i/violations/:projectUuid',
namedParams: {
projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87',
},
staticPreview: this.renderViolationsBadge({
numViolations: 0,
}),
keywords,
},
]
}
async fetch({ projectUuid }) {
const url = `https://insight.symfony.com/api/projects/${projectUuid}`
const options = {
headers: {
Accept: 'application/vnd.com.sensiolabs.insight+xml',
},
}
if (
!serverSecrets.sl_insight_userUuid ||
!serverSecrets.sl_insight_apiToken
) {
throw new Inaccessible({
prettyMessage: 'required API tokens not found in config',
})
}
options.auth = {
user: serverSecrets.sl_insight_userUuid,
pass: serverSecrets.sl_insight_apiToken,
}
return this._requestXml({
url,
options,
schema,
errorMessages: {
401: 'not authorized to access project',
404: 'project not found',
},
parserOptions: {
attributeNamePrefix: '',
ignoreAttributes: false,
},
})
}
transform({ data }) {
const lastAnalysis = data.project['last-analysis']
let numViolations = 0
let numCriticalViolations = 0
let numMajorViolations = 0
let numMinorViolations = 0
let numInfoViolations = 0
const violationContainer = lastAnalysis.violations
if (violationContainer && violationContainer.violation) {
let violations = []
// See above note on schema RE: https://github.com/NaturalIntelligence/fast-xml-parser/issues/68
// This covers the scenario of multiple violations which are parsed as an array and single
// violations which is parsed as a single object.
if (Array.isArray(violationContainer.violation)) {
violations = violationContainer.violation
} else {
violations.push(violationContainer.violation)
}
numViolations = violations.length
violations.forEach(violation => {
if (violation.severity === 'critical') {
numCriticalViolations++
} else if (violation.severity === 'major') {
numMajorViolations++
} else if (violation.severity === 'minor') {
numMinorViolations++
} else {
numInfoViolations++
}
})
}
return {
status: lastAnalysis.status,
grade: lastAnalysis.grade,
numViolations,
numCriticalViolations,
numMajorViolations,
numMinorViolations,
numInfoViolations,
}
}
async handle({ metric = 'grade', projectUuid }) {
const data = await this.fetch({ projectUuid })
const {
status,
grade,
numViolations,
numCriticalViolations,
numMajorViolations,
numMinorViolations,
numInfoViolations,
} = this.transform({ data })
return this.constructor.render({
metric,
status,
grade,
numViolations,
numCriticalViolations,
numMajorViolations,
numMinorViolations,
numInfoViolations,
})
}
}

View File

@@ -1,318 +0,0 @@
'use strict'
const Joi = require('joi')
const { withRegex } = require('../test-validators')
const t = (module.exports = require('../tester').createServiceTester())
const {
runningMockResponse,
platinumMockResponse,
goldMockResponse,
silverMockResponse,
bronzeMockResponse,
noMedalMockResponse,
mockSymfonyUser,
mockSymfonyToken,
mockSymfonyInsightCreds,
setSymfonyInsightCredsToFalsy,
restore,
realTokenExists,
prepLiveTest,
criticalViolation,
majorViolation,
minorViolation,
infoViolation,
multipleViolations,
} = require('./symfony-test-helpers')
const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87'
function create(title, { withMockCreds = true } = { withMockCreds: true }) {
const result = t.create(title)
if (withMockCreds) {
result.before(mockSymfonyInsightCreds)
result.finally(restore)
}
return result
}
create('live: valid project grade', { withMockCreds: false })
.before(prepLiveTest)
.get(`/symfony/i/grade/${sampleProjectUuid}.json`)
.timeout(15000)
.interceptIf(!realTokenExists, nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, platinumMockResponse)
)
.expectJSONTypes(
Joi.object().keys({
name: 'grade',
value: Joi.equal(
'platinum',
'gold',
'silver',
'bronze',
'no medal'
).required(),
})
)
create('live: valid project violations', { withMockCreds: false })
.before(prepLiveTest)
.get(`/symfony/i/violations/${sampleProjectUuid}.json`)
.timeout(15000)
.interceptIf(!realTokenExists, nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, multipleViolations)
)
.expectJSONTypes(
Joi.object().keys({
name: 'violations',
value: withRegex(
/\d* critical|\d* critical, \d* major|\d* critical, \d* major, \d* minor|\d* critical, \d* major, \d* minor, \d* info|\d* critical, \d* minor|\d* critical, \d* info|\d* major|\d* major, \d* minor|\d* major, \d* minor, \d* info|\d* major, \d* info|\d* minor|\d* minor, \d* info/
),
})
)
create('live: nonexistent project', { withMockCreds: false })
.before(prepLiveTest)
.get('/symfony/i/grade/45afb680-d4e6-4e66-93ea-bcfa79eb8a88.json')
.interceptIf(!realTokenExists, nock =>
nock('https://insight.symfony.com/api/projects')
.get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88')
.reply(404)
)
.expectJSON({
name: 'symfony insight',
value: 'project not found',
})
create('404 project not found grade')
.get(`/symfony/i/grade/${sampleProjectUuid}.json`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(404)
)
.expectJSON({
name: 'symfony insight',
value: 'project not found',
})
create('401 not authorized grade')
.get(`/symfony/i/grade/${sampleProjectUuid}.json`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(401)
)
.expectJSON({
name: 'symfony insight',
value: 'not authorized to access project',
})
create('pending project grade')
.get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, runningMockResponse)
)
.expectJSON({
name: 'grade',
value: 'pending',
color: 'lightgrey',
})
create('platinum grade')
.get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, platinumMockResponse)
)
.expectJSON({
name: 'grade',
value: 'platinum',
color: '#e5e4e2',
})
create('gold grade')
.get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, goldMockResponse)
)
.expectJSON({
name: 'grade',
value: 'gold',
color: '#ebc760',
})
create('silver grade')
.get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, silverMockResponse)
)
.expectJSON({
name: 'grade',
value: 'silver',
color: '#c0c0c0',
})
create('bronze grade')
.get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, bronzeMockResponse)
)
.expectJSON({
name: 'grade',
value: 'bronze',
color: '#c88f6a',
})
create('no medal grade')
.get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, noMedalMockResponse)
)
.expectJSON({
name: 'grade',
value: 'no medal',
color: 'red',
})
create('zero violations')
.get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, goldMockResponse)
)
.expectJSON({
name: 'violations',
value: '0',
color: 'brightgreen',
})
create('critical violations')
.get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, criticalViolation)
)
.expectJSON({
name: 'violations',
value: '1 critical',
color: 'red',
})
create('major violations')
.get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, majorViolation)
)
.expectJSON({
name: 'violations',
value: '1 major',
color: 'orange',
})
create('minor violations')
.get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.basicAuth({
user: mockSymfonyUser,
pass: mockSymfonyToken,
})
.reply(200, minorViolation)
)
.expectJSON({
name: 'violations',
value: '1 minor',
color: 'yellow',
})
create('info violations')
.get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.basicAuth({
user: mockSymfonyUser,
pass: mockSymfonyToken,
})
.reply(200, infoViolation)
)
.expectJSON({
name: 'violations',
value: '1 info',
color: 'yellowgreen',
})
create('multiple violations grade')
.get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.basicAuth({
user: mockSymfonyUser,
pass: mockSymfonyToken,
})
.reply(200, multipleViolations)
)
.expectJSON({
name: 'violations',
value: '1 critical, 1 info',
color: 'red',
})
create('auth missing', { withMockCreds: false })
.before(setSymfonyInsightCredsToFalsy)
.get(`/symfony/i/grade/${sampleProjectUuid}.json`)
.expectJSON({
name: 'symfony insight',
value: 'required API tokens not found in config',
})
// These tests ensure that the legacy badge path (/sensiolabs/i/projectUuid) still works
create('legacy path: pending project grade')
.get(`/sensiolabs/i/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, runningMockResponse)
)
.expectJSON({
name: 'grade',
value: 'pending',
color: 'lightgrey',
})
create('legacy path: platinum grade')
.get(`/sensiolabs/i/${sampleProjectUuid}.json?style=_shields_test`)
.intercept(nock =>
nock('https://insight.symfony.com/api/projects')
.get(`/${sampleProjectUuid}`)
.reply(200, platinumMockResponse)
)
.expectJSON({
name: 'grade',
value: 'platinum',
color: '#e5e4e2',
})

View File

@@ -3,6 +3,8 @@
const sinon = require('sinon')
const serverSecrets = require('../../lib/server-secrets')
const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87'
function createMockResponse({ status = 'finished', grade, violations }) {
let response = `
<project>
@@ -112,7 +114,21 @@ function prepLiveTest() {
}
}
function createTest(
t,
title,
{ withMockCreds = true } = { withMockCreds: true }
) {
const result = t.create(title)
if (withMockCreds) {
result.before(mockSymfonyInsightCreds)
result.finally(restore)
}
return result
}
module.exports = {
sampleProjectUuid,
runningMockResponse,
platinumMockResponse,
goldMockResponse,
@@ -131,4 +147,5 @@ module.exports = {
minorViolation,
infoViolation,
multipleViolations,
createTest,
}