From 6bfb70b4bc78735ff704a0fad43614a37c029c96 Mon Sep 17 00:00:00 2001 From: calebcartwright Date: Fri, 21 Dec 2018 00:44:44 -0600 Subject: [PATCH 1/9] feat: migrate Sensiolabs/Symfony Insight to new service model --- services/sensiolabs/sensiolabs-helpers.js | 48 ++++++ services/sensiolabs/sensiolabs.service.js | 175 ++++++++++++---------- services/sensiolabs/sensiolabs.tester.js | 151 +++++++++++++++++++ 3 files changed, 291 insertions(+), 83 deletions(-) create mode 100644 services/sensiolabs/sensiolabs-helpers.js create mode 100644 services/sensiolabs/sensiolabs.tester.js diff --git a/services/sensiolabs/sensiolabs-helpers.js b/services/sensiolabs/sensiolabs-helpers.js new file mode 100644 index 0000000000000..8f80f12dfc346 --- /dev/null +++ b/services/sensiolabs/sensiolabs-helpers.js @@ -0,0 +1,48 @@ +'use strict' + +const statusRegex = /ordered|running|measured|analyzed|finished/ + +function createMockResponse({ status, grade }) { + return ` + + + + + ${grade ? `` : ''} + + ` +} + +const runningMockResponse = createMockResponse({ + status: 'running', +}) +const platinumMockResponse = createMockResponse({ + status: 'finished', + grade: 'platinum', +}) +const goldMockResponse = createMockResponse({ + status: 'finished', + grade: 'gold', +}) +const silverMockResponse = createMockResponse({ + status: 'finished', + grade: 'silver', +}) +const bronzeMockResponse = createMockResponse({ + status: 'finished', + grade: 'bronze', +}) +const noMedalMockResponse = createMockResponse({ + status: 'finished', + grade: 'none', +}) + +module.exports = { + runningMockResponse, + platinumMockResponse, + goldMockResponse, + silverMockResponse, + bronzeMockResponse, + noMedalMockResponse, + statusRegex, +} diff --git a/services/sensiolabs/sensiolabs.service.js b/services/sensiolabs/sensiolabs.service.js index 5b3bab5cce473..ec141c443bff0 100644 --- a/services/sensiolabs/sensiolabs.service.js +++ b/services/sensiolabs/sensiolabs.service.js @@ -1,23 +1,66 @@ 'use strict' -const LegacyService = require('../legacy-service') -const { makeBadgeData: getBadgeData } = require('../../lib/badge-data') +const Joi = require('joi') +const BaseXmlService = require('../base-xml') const serverSecrets = require('../../lib/server-secrets') +const { statusRegex } = require('./sensiolabs-helpers') + +const schema = Joi.object({ + project: Joi.object({ + 'last-analysis': Joi.object({ + status: Joi.string() + .regex(statusRegex) + .required(), + grade: Joi.string() + .regex(/platinum|gold|silver|bronze|none/) + .allow('', null), + }), + }).required(), +}).required() + +module.exports = class Sensiolabs extends BaseXmlService { + static render({ status, grade }) { + if (status !== 'finished') { + return { + message: 'pending', + color: 'grey', + } + } + let color, + message = grade + if (grade === 'platinum') { + color = 'brightgreen' + } else if (grade === 'gold') { + color = 'yellow' + } else if (grade === 'silver') { + color = 'lightgrey' + } else if (grade === 'bronze') { + color = 'orange' + } else { + message = 'no medal' + color = 'red' + } + + return { + message, + color, + } + } -// 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 Sensiolabs extends LegacyService { static get category() { return 'build' } static get route() { return { - base: 'sensiolabs', + base: 'sensiolabs/i', + pattern: ':projectUuid', + } + } + + static get defaultBadgeData() { + return { + label: 'check', } } @@ -25,86 +68,52 @@ module.exports = class Sensiolabs extends LegacyService { return [ { title: 'SensioLabs Insight', - previewUrl: 'i/45afb680-d4e6-4e66-93ea-bcfa79eb8a87', + pattern: ':projectUuid', + namedParams: { + projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87', + }, + staticPreview: this.render({ + status: 'finished', + grade: 'bronze', + }), }, ] } - static registerLegacyRouteHandler({ camp, cache }) { - camp.route( - /^\/sensiolabs\/i\/([^/]+)\.(svg|png|gif|jpg|json)$/, - cache((data, match, sendBadge, request) => { - const projectUuid = match[1] - const format = match[2] - const options = { - method: 'GET', - uri: `https://insight.sensiolabs.com/api/projects/${projectUuid}`, - headers: { - Accept: 'application/vnd.com.sensiolabs.insight+xml', - }, - } - - if (serverSecrets && serverSecrets.sl_insight_userUuid) { - options.auth = { - user: serverSecrets.sl_insight_userUuid, - pass: serverSecrets.sl_insight_apiToken, - } - } - - const badgeData = getBadgeData('check', data) - - request(options, (err, res, body) => { - if (err != null || res.statusCode !== 200) { - badgeData.text[1] = 'inaccessible' - sendBadge(format, badgeData) - return - } + async fetch({ projectUuid }) { + const url = `https://insight.sensiolabs.com/api/projects/${projectUuid}` + const options = { + headers: { + Accept: 'application/vnd.com.sensiolabs.insight+xml', + }, + } - const matchStatus = body.match( - /<\/status>/im - ) - const matchGrade = body.match( - /<\/grade>/im - ) + if (serverSecrets && serverSecrets.sl_insight_userUuid) { + options.auth = { + user: serverSecrets.sl_insight_userUuid, + pass: serverSecrets.sl_insight_apiToken, + } + } - if (matchStatus === null) { - badgeData.text[1] = 'inaccessible' - sendBadge(format, badgeData) - return - } else if (matchStatus[1] !== 'finished') { - badgeData.text[1] = 'pending' - sendBadge(format, badgeData) - return - } else if (matchGrade === null) { - badgeData.text[1] = 'invalid' - sendBadge(format, badgeData) - return - } + return this._requestXml({ + url, + options, + schema, + errorMessages: { + 401: 'not authorized to access project', + 404: 'project not found', + }, + }) + } - if (matchGrade[1] === 'platinum') { - badgeData.text[1] = 'platinum' - badgeData.colorscheme = 'brightgreen' - } else if (matchGrade[1] === 'gold') { - badgeData.text[1] = 'gold' - badgeData.colorscheme = 'yellow' - } else if (matchGrade[1] === 'silver') { - badgeData.text[1] = 'silver' - badgeData.colorscheme = 'lightgrey' - } else if (matchGrade[1] === 'bronze') { - badgeData.text[1] = 'bronze' - badgeData.colorscheme = 'orange' - } else if (matchGrade[1] === 'none') { - badgeData.text[1] = 'no medal' - badgeData.colorscheme = 'red' - } else { - badgeData.text[1] = 'invalid' - sendBadge(format, badgeData) - return - } + transform({ data }) { + const lastAnalysis = data.project['last-analysis'] + return { status: lastAnalysis.status, grade: lastAnalysis.grade } + } - sendBadge(format, badgeData) - }) - }) - ) + async handle({ projectUuid }) { + const data = await this.fetch({ projectUuid }) + const { status, grade } = this.transform({ data }) + return this.constructor.render({ status, grade }) } } diff --git a/services/sensiolabs/sensiolabs.tester.js b/services/sensiolabs/sensiolabs.tester.js new file mode 100644 index 0000000000000..47378c6b03da3 --- /dev/null +++ b/services/sensiolabs/sensiolabs.tester.js @@ -0,0 +1,151 @@ +'use strict' + +// const Joi = require('joi') +const sinon = require('sinon') +const { colorScheme } = require('../test-helpers') +const t = (module.exports = require('../create-service-tester')()) +const serverSecrets = require('../../lib/server-secrets') +const { + // statusRegex, + runningMockResponse, + platinumMockResponse, + goldMockResponse, + silverMockResponse, + bronzeMockResponse, + noMedalMockResponse, +} = require('./sensiolabs-helpers') + +// const messageRegex = /platinum|gold|silver|bronze|no medal/ +const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87' +const mockSLUser = 'admin' +const mockSLApiToken = 'password' + +function mockSensiolabsCreds() { + serverSecrets['sl_insight_userUuid'] = undefined + serverSecrets['sl_insight_apiToken'] = undefined + sinon.stub(serverSecrets, 'sl_insight_userUuid').value(mockSLUser) + sinon.stub(serverSecrets, 'sl_insight_apiToken').value(mockSLApiToken) +} + +t.create('404 project not found') + .get(`/${sampleProjectUuid}.json`) + .intercept(nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(404) + ) + .expectJSON({ + name: 'check', + value: 'project not found', + }) + +t.create('401 not authorized') + .get(`/${sampleProjectUuid}.json`) + .intercept(nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(401) + ) + .expectJSON({ + name: 'check', + value: 'not authorized to access project', + }) + +t.create('pending project') + .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, runningMockResponse) + ) + .expectJSON({ + name: 'check', + value: 'pending', + colorB: colorScheme.grey, + }) + +t.create('platinum medal project') + .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, platinumMockResponse) + ) + .expectJSON({ + name: 'check', + value: 'platinum', + colorB: colorScheme.brightgreen, + }) + +t.create('gold medal project') + .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, goldMockResponse) + ) + .expectJSON({ + name: 'check', + value: 'gold', + colorB: colorScheme.yellow, + }) + +t.create('silver medal project') + .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, silverMockResponse) + ) + .expectJSON({ + name: 'check', + value: 'silver', + colorB: colorScheme.lightgrey, + }) + +t.create('bronze medal project') + .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, bronzeMockResponse) + ) + .expectJSON({ + name: 'check', + value: 'bronze', + colorB: colorScheme.orange, + }) + +t.create('no medal project') + .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, noMedalMockResponse) + ) + .expectJSON({ + name: 'check', + value: 'no medal', + colorB: colorScheme.red, + }) + +t.create('auth') + .before(mockSensiolabsCreds) + .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + // 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: mockSLUser, + pass: mockSLApiToken, + }) + .reply(200, bronzeMockResponse) + ) + .expectJSON({ + name: 'check', + value: 'bronze', + colorB: colorScheme.orange, + }) + .finally(sinon.restore) From 7be15cf582a408eac3a8bbd2a3ca1ae97bac4b6a Mon Sep 17 00:00:00 2001 From: calebcartwright Date: Fri, 21 Dec 2018 14:57:33 -0600 Subject: [PATCH 2/9] tests: added some live (conditionally) tests for sensiolabs service --- ...-helpers.js => sensiolabs-test-helpers.js} | 29 +++++++++- services/sensiolabs/sensiolabs.service.js | 3 +- services/sensiolabs/sensiolabs.tester.js | 55 ++++++++++++++----- 3 files changed, 68 insertions(+), 19 deletions(-) rename services/sensiolabs/{sensiolabs-helpers.js => sensiolabs-test-helpers.js} (56%) diff --git a/services/sensiolabs/sensiolabs-helpers.js b/services/sensiolabs/sensiolabs-test-helpers.js similarity index 56% rename from services/sensiolabs/sensiolabs-helpers.js rename to services/sensiolabs/sensiolabs-test-helpers.js index 8f80f12dfc346..d8364fd5f2457 100644 --- a/services/sensiolabs/sensiolabs-helpers.js +++ b/services/sensiolabs/sensiolabs-test-helpers.js @@ -1,6 +1,7 @@ 'use strict' -const statusRegex = /ordered|running|measured|analyzed|finished/ +const sinon = require('sinon') +const serverSecrets = require('../../lib/server-secrets') function createMockResponse({ status, grade }) { return ` @@ -37,6 +38,25 @@ const noMedalMockResponse = createMockResponse({ grade: 'none', }) +const mockSLUser = 'admin' +const mockSLApiToken = 'password' + +function mockSensiolabsCreds() { + serverSecrets['sl_insight_userUuid'] = undefined + serverSecrets['sl_insight_apiToken'] = undefined + sinon.stub(serverSecrets, 'sl_insight_userUuid').value(mockSLUser) + sinon.stub(serverSecrets, 'sl_insight_apiToken').value(mockSLApiToken) +} + +const tokenExists = serverSecrets.sl_insight_userUuid +function logTokenWarning() { + if (!tokenExists) { + console.warn( + 'No token provided, this test will mock Symfony Insight API responses.' + ) + } +} + module.exports = { runningMockResponse, platinumMockResponse, @@ -44,5 +64,10 @@ module.exports = { silverMockResponse, bronzeMockResponse, noMedalMockResponse, - statusRegex, + mockSLUser, + mockSLApiToken, + mockSensiolabsCreds, + restore: sinon.restore, + tokenExists, + logTokenWarning, } diff --git a/services/sensiolabs/sensiolabs.service.js b/services/sensiolabs/sensiolabs.service.js index ec141c443bff0..77a40be7bd878 100644 --- a/services/sensiolabs/sensiolabs.service.js +++ b/services/sensiolabs/sensiolabs.service.js @@ -3,13 +3,12 @@ const Joi = require('joi') const BaseXmlService = require('../base-xml') const serverSecrets = require('../../lib/server-secrets') -const { statusRegex } = require('./sensiolabs-helpers') const schema = Joi.object({ project: Joi.object({ 'last-analysis': Joi.object({ status: Joi.string() - .regex(statusRegex) + .regex(/ordered|running|measured|analyzed|finished/) .required(), grade: Joi.string() .regex(/platinum|gold|silver|bronze|none/) diff --git a/services/sensiolabs/sensiolabs.tester.js b/services/sensiolabs/sensiolabs.tester.js index 47378c6b03da3..a6b59cf452d25 100644 --- a/services/sensiolabs/sensiolabs.tester.js +++ b/services/sensiolabs/sensiolabs.tester.js @@ -1,31 +1,56 @@ 'use strict' -// const Joi = require('joi') -const sinon = require('sinon') +const Joi = require('joi') const { colorScheme } = require('../test-helpers') const t = (module.exports = require('../create-service-tester')()) -const serverSecrets = require('../../lib/server-secrets') +const { withRegex } = require('../test-validators') + const { - // statusRegex, runningMockResponse, platinumMockResponse, goldMockResponse, silverMockResponse, bronzeMockResponse, noMedalMockResponse, -} = require('./sensiolabs-helpers') + mockSLUser, + mockSLApiToken, + mockSensiolabsCreds, + restore, + tokenExists, + logTokenWarning, +} = require('./sensiolabs-test-helpers') -// const messageRegex = /platinum|gold|silver|bronze|no medal/ +const gradeRegex = /platinum|gold|silver|bronze|no medal/ const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87' -const mockSLUser = 'admin' -const mockSLApiToken = 'password' -function mockSensiolabsCreds() { - serverSecrets['sl_insight_userUuid'] = undefined - serverSecrets['sl_insight_apiToken'] = undefined - sinon.stub(serverSecrets, 'sl_insight_userUuid').value(mockSLUser) - sinon.stub(serverSecrets, 'sl_insight_apiToken').value(mockSLApiToken) -} +t.create('live: valid project') + .before(logTokenWarning) + .get(`/${sampleProjectUuid}.json`) + .timeout(10000) + .interceptIf(!tokenExists, nock => + nock('https://insight.sensiolabs.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, platinumMockResponse) + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'check', + value: withRegex(gradeRegex), + }) + ) + +t.create('live: nonexistent project') + .before(logTokenWarning) + .get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88.json') + .interceptIf(!tokenExists, nock => + nock('https://insight.sensiolabs.com/api/projects') + .get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88') + .reply(404) + ) + .expectJSON({ + name: 'check', + value: 'project not found', + }) t.create('404 project not found') .get(`/${sampleProjectUuid}.json`) @@ -148,4 +173,4 @@ t.create('auth') value: 'bronze', colorB: colorScheme.orange, }) - .finally(sinon.restore) + .finally(restore) From 5d4006a6e0751ed415c4fc3731839f8fecfd659f Mon Sep 17 00:00:00 2001 From: calebcartwright Date: Sat, 22 Dec 2018 22:35:26 -0500 Subject: [PATCH 3/9] feat: added violations badge for sensiolabs/symfony insight --- .../sensiolabs/sensiolabs-test-helpers.js | 5 +- services/sensiolabs/sensiolabs.service.js | 166 ++++++++++++++++-- services/sensiolabs/sensiolabs.tester.js | 44 ++--- 3 files changed, 179 insertions(+), 36 deletions(-) diff --git a/services/sensiolabs/sensiolabs-test-helpers.js b/services/sensiolabs/sensiolabs-test-helpers.js index d8364fd5f2457..15adf81b6529b 100644 --- a/services/sensiolabs/sensiolabs-test-helpers.js +++ b/services/sensiolabs/sensiolabs-test-helpers.js @@ -8,7 +8,10 @@ function createMockResponse({ status, grade }) { - + + foo + bar + ${grade ? `` : ''} ` diff --git a/services/sensiolabs/sensiolabs.service.js b/services/sensiolabs/sensiolabs.service.js index 77a40be7bd878..73812fbac357c 100644 --- a/services/sensiolabs/sensiolabs.service.js +++ b/services/sensiolabs/sensiolabs.service.js @@ -13,18 +13,55 @@ const schema = Joi.object({ grade: Joi.string() .regex(/platinum|gold|silver|bronze|none/) .allow('', null), + violations: Joi.object({ + violation: Joi.array() + .optional() + .items( + Joi.object({ + severity: Joi.string() + .regex(/info|minor|major|critical/) + .required(), + }) + ), + }) + .optional() + .allow(''), }), }).required(), }).required() module.exports = class Sensiolabs extends BaseXmlService { - static render({ status, grade }) { + static render({ + metric, + status, + grade, + numViolations, + numCriticalViolations, + numMajorViolations, + numMinorViolations, + numInfoViolations, + }) { if (status !== 'finished') { return { message: 'pending', color: 'grey', } } + + 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') { @@ -46,36 +83,85 @@ module.exports = class Sensiolabs extends BaseXmlService { } } + static renderViolationsBadge({ + numViolations, + numCriticalViolations, + numMajorViolations, + numMinorViolations, + numInfoViolations, + }) { + if (numViolations === 0) { + return { + message: '0 violations', + 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} major`) + color = 'red' + } + + return { + message: violationSummary.join(', '), + color, + } + } + static get category() { - return 'build' + return 'quality' } static get route() { return { - base: 'sensiolabs/i', - pattern: ':projectUuid', + // base: '(symfony|sensiolabs)/i', + // pattern: ':metric(grade|violations)?/:projectUuid', + pattern: '(symfony|sensiolabs)/i/:metric(grade|violations)?/:projectUuid', } } static get defaultBadgeData() { return { - label: 'check', + label: 'checks', } } static get examples() { return [ { - title: 'SensioLabs Insight', - pattern: ':projectUuid', + title: 'SensioLabs Insight Grade', + pattern: 'grade/:projectUuid', namedParams: { projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87', }, - staticPreview: this.render({ - status: 'finished', + staticPreview: this.renderGradeBadge({ grade: 'bronze', }), }, + { + title: 'SensioLabs Insight Violations', + pattern: 'violations/:projectUuid', + namedParams: { + projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87', + }, + staticPreview: this.renderGradeBadge({ + numViolations: 0, + }), + }, ] } @@ -102,17 +188,71 @@ module.exports = class Sensiolabs extends BaseXmlService { 401: 'not authorized to access project', 404: 'project not found', }, + parserOptions: { + attributeNamePrefix: '', + ignoreAttributes: false, + }, }) } transform({ data }) { const lastAnalysis = data.project['last-analysis'] - return { status: lastAnalysis.status, grade: lastAnalysis.grade } + let numViolations = 0 + let numCriticalViolations = 0 + let numMajorViolations = 0 + let numMinorViolations = 0 + let numInfoViolations = 0 + + const violationContainer = lastAnalysis.violations + + if (violationContainer && violationContainer.violations) { + const violations = violationContainer.violations + 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({ projectUuid }) { + async handle({ metric = 'grade', projectUuid }) { const data = await this.fetch({ projectUuid }) - const { status, grade } = this.transform({ data }) - return this.constructor.render({ status, grade }) + const { + status, + grade, + numViolations, + numCriticalViolations, + numMajorViolations, + numMinorViolations, + numInfoViolations, + } = this.transform({ data }) + + return this.constructor.render({ + metric, + status, + grade, + numViolations, + numCriticalViolations, + numMajorViolations, + numMinorViolations, + numInfoViolations, + }) } } diff --git a/services/sensiolabs/sensiolabs.tester.js b/services/sensiolabs/sensiolabs.tester.js index a6b59cf452d25..1ac45de996537 100644 --- a/services/sensiolabs/sensiolabs.tester.js +++ b/services/sensiolabs/sensiolabs.tester.js @@ -25,7 +25,7 @@ const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87' t.create('live: valid project') .before(logTokenWarning) - .get(`/${sampleProjectUuid}.json`) + .get(`/sensiolabs/i/${sampleProjectUuid}.json`) .timeout(10000) .interceptIf(!tokenExists, nock => nock('https://insight.sensiolabs.com/api/projects') @@ -34,129 +34,129 @@ t.create('live: valid project') ) .expectJSONTypes( Joi.object().keys({ - name: 'check', + name: 'checks', value: withRegex(gradeRegex), }) ) t.create('live: nonexistent project') .before(logTokenWarning) - .get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88.json') + .get('/sensiolabs/i/45afb680-d4e6-4e66-93ea-bcfa79eb8a88.json') .interceptIf(!tokenExists, nock => nock('https://insight.sensiolabs.com/api/projects') .get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88') .reply(404) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'project not found', }) t.create('404 project not found') - .get(`/${sampleProjectUuid}.json`) + .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json`) .intercept(nock => nock('https://insight.sensiolabs.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(404) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'project not found', }) t.create('401 not authorized') - .get(`/${sampleProjectUuid}.json`) + .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json`) .intercept(nock => nock('https://insight.sensiolabs.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(401) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'not authorized to access project', }) t.create('pending project') - .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.sensiolabs.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, runningMockResponse) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'pending', colorB: colorScheme.grey, }) t.create('platinum medal project') - .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.sensiolabs.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, platinumMockResponse) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'platinum', colorB: colorScheme.brightgreen, }) t.create('gold medal project') - .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.sensiolabs.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, goldMockResponse) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'gold', colorB: colorScheme.yellow, }) t.create('silver medal project') - .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.sensiolabs.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, silverMockResponse) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'silver', colorB: colorScheme.lightgrey, }) t.create('bronze medal project') - .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.sensiolabs.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, bronzeMockResponse) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'bronze', colorB: colorScheme.orange, }) t.create('no medal project') - .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.sensiolabs.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, noMedalMockResponse) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'no medal', colorB: colorScheme.red, }) t.create('auth') .before(mockSensiolabsCreds) - .get(`/${sampleProjectUuid}.json?style=_shields_test`) + .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.sensiolabs.com/api/projects') .get(`/${sampleProjectUuid}`) @@ -169,7 +169,7 @@ t.create('auth') .reply(200, bronzeMockResponse) ) .expectJSON({ - name: 'check', + name: 'checks', value: 'bronze', colorB: colorScheme.orange, }) From 8db37b4e4d09e6f389872ca903a4f522e8233ce6 Mon Sep 17 00:00:00 2001 From: calebcartwright Date: Mon, 24 Dec 2018 00:20:05 -0500 Subject: [PATCH 4/9] finished symfonyinsight/sensiolabs refactor and enhancements --- .../sensiolabs/sensiolabs-test-helpers.js | 76 ----- services/sensiolabs/sensiolabs.tester.js | 176 ---------- .../symfony-insight.service.js} | 85 +++-- services/symfony/symfony-insight.tester.js | 304 ++++++++++++++++++ services/symfony/symfony-test-helpers.js | 116 +++++++ 5 files changed, 472 insertions(+), 285 deletions(-) delete mode 100644 services/sensiolabs/sensiolabs-test-helpers.js delete mode 100644 services/sensiolabs/sensiolabs.tester.js rename services/{sensiolabs/sensiolabs.service.js => symfony/symfony-insight.service.js} (68%) create mode 100644 services/symfony/symfony-insight.tester.js create mode 100644 services/symfony/symfony-test-helpers.js diff --git a/services/sensiolabs/sensiolabs-test-helpers.js b/services/sensiolabs/sensiolabs-test-helpers.js deleted file mode 100644 index 15adf81b6529b..0000000000000 --- a/services/sensiolabs/sensiolabs-test-helpers.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict' - -const sinon = require('sinon') -const serverSecrets = require('../../lib/server-secrets') - -function createMockResponse({ status, grade }) { - return ` - - - - - foo - bar - - ${grade ? `` : ''} - - ` -} - -const runningMockResponse = createMockResponse({ - status: 'running', -}) -const platinumMockResponse = createMockResponse({ - status: 'finished', - grade: 'platinum', -}) -const goldMockResponse = createMockResponse({ - status: 'finished', - grade: 'gold', -}) -const silverMockResponse = createMockResponse({ - status: 'finished', - grade: 'silver', -}) -const bronzeMockResponse = createMockResponse({ - status: 'finished', - grade: 'bronze', -}) -const noMedalMockResponse = createMockResponse({ - status: 'finished', - grade: 'none', -}) - -const mockSLUser = 'admin' -const mockSLApiToken = 'password' - -function mockSensiolabsCreds() { - serverSecrets['sl_insight_userUuid'] = undefined - serverSecrets['sl_insight_apiToken'] = undefined - sinon.stub(serverSecrets, 'sl_insight_userUuid').value(mockSLUser) - sinon.stub(serverSecrets, 'sl_insight_apiToken').value(mockSLApiToken) -} - -const tokenExists = serverSecrets.sl_insight_userUuid -function logTokenWarning() { - if (!tokenExists) { - console.warn( - 'No token provided, this test will mock Symfony Insight API responses.' - ) - } -} - -module.exports = { - runningMockResponse, - platinumMockResponse, - goldMockResponse, - silverMockResponse, - bronzeMockResponse, - noMedalMockResponse, - mockSLUser, - mockSLApiToken, - mockSensiolabsCreds, - restore: sinon.restore, - tokenExists, - logTokenWarning, -} diff --git a/services/sensiolabs/sensiolabs.tester.js b/services/sensiolabs/sensiolabs.tester.js deleted file mode 100644 index 1ac45de996537..0000000000000 --- a/services/sensiolabs/sensiolabs.tester.js +++ /dev/null @@ -1,176 +0,0 @@ -'use strict' - -const Joi = require('joi') -const { colorScheme } = require('../test-helpers') -const t = (module.exports = require('../create-service-tester')()) -const { withRegex } = require('../test-validators') - -const { - runningMockResponse, - platinumMockResponse, - goldMockResponse, - silverMockResponse, - bronzeMockResponse, - noMedalMockResponse, - mockSLUser, - mockSLApiToken, - mockSensiolabsCreds, - restore, - tokenExists, - logTokenWarning, -} = require('./sensiolabs-test-helpers') - -const gradeRegex = /platinum|gold|silver|bronze|no medal/ -const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87' - -t.create('live: valid project') - .before(logTokenWarning) - .get(`/sensiolabs/i/${sampleProjectUuid}.json`) - .timeout(10000) - .interceptIf(!tokenExists, nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - .reply(200, platinumMockResponse) - ) - .expectJSONTypes( - Joi.object().keys({ - name: 'checks', - value: withRegex(gradeRegex), - }) - ) - -t.create('live: nonexistent project') - .before(logTokenWarning) - .get('/sensiolabs/i/45afb680-d4e6-4e66-93ea-bcfa79eb8a88.json') - .interceptIf(!tokenExists, nock => - nock('https://insight.sensiolabs.com/api/projects') - .get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88') - .reply(404) - ) - .expectJSON({ - name: 'checks', - value: 'project not found', - }) - -t.create('404 project not found') - .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json`) - .intercept(nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - .reply(404) - ) - .expectJSON({ - name: 'checks', - value: 'project not found', - }) - -t.create('401 not authorized') - .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json`) - .intercept(nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - .reply(401) - ) - .expectJSON({ - name: 'checks', - value: 'not authorized to access project', - }) - -t.create('pending project') - .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) - .intercept(nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - .reply(200, runningMockResponse) - ) - .expectJSON({ - name: 'checks', - value: 'pending', - colorB: colorScheme.grey, - }) - -t.create('platinum medal project') - .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) - .intercept(nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - .reply(200, platinumMockResponse) - ) - .expectJSON({ - name: 'checks', - value: 'platinum', - colorB: colorScheme.brightgreen, - }) - -t.create('gold medal project') - .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) - .intercept(nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - .reply(200, goldMockResponse) - ) - .expectJSON({ - name: 'checks', - value: 'gold', - colorB: colorScheme.yellow, - }) - -t.create('silver medal project') - .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) - .intercept(nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - .reply(200, silverMockResponse) - ) - .expectJSON({ - name: 'checks', - value: 'silver', - colorB: colorScheme.lightgrey, - }) - -t.create('bronze medal project') - .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) - .intercept(nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - .reply(200, bronzeMockResponse) - ) - .expectJSON({ - name: 'checks', - value: 'bronze', - colorB: colorScheme.orange, - }) - -t.create('no medal project') - .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) - .intercept(nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - .reply(200, noMedalMockResponse) - ) - .expectJSON({ - name: 'checks', - value: 'no medal', - colorB: colorScheme.red, - }) - -t.create('auth') - .before(mockSensiolabsCreds) - .get(`/sensiolabs/i/grade/${sampleProjectUuid}.json?style=_shields_test`) - .intercept(nock => - nock('https://insight.sensiolabs.com/api/projects') - .get(`/${sampleProjectUuid}`) - // 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: mockSLUser, - pass: mockSLApiToken, - }) - .reply(200, bronzeMockResponse) - ) - .expectJSON({ - name: 'checks', - value: 'bronze', - colorB: colorScheme.orange, - }) - .finally(restore) diff --git a/services/sensiolabs/sensiolabs.service.js b/services/symfony/symfony-insight.service.js similarity index 68% rename from services/sensiolabs/sensiolabs.service.js rename to services/symfony/symfony-insight.service.js index 73812fbac357c..900ae62c33fef 100644 --- a/services/sensiolabs/sensiolabs.service.js +++ b/services/symfony/symfony-insight.service.js @@ -4,33 +4,39 @@ const Joi = require('joi') const BaseXmlService = require('../base-xml') const serverSecrets = require('../../lib/server-secrets') +const violationSchema = Joi.object({ + severity: Joi.string() + .regex(/info|minor|major|critical/) + .required(), +}).required() + const schema = Joi.object({ project: Joi.object({ 'last-analysis': Joi.object({ status: Joi.string() .regex(/ordered|running|measured|analyzed|finished/) .required(), - grade: Joi.string() - .regex(/platinum|gold|silver|bronze|none/) - .allow('', null), + grade: Joi.string().regex(/platinum|gold|silver|bronze|none/), violations: Joi.object({ - violation: Joi.array() - .optional() - .items( - Joi.object({ - severity: Joi.string() - .regex(/info|minor|major|critical/) - .required(), - }) - ), - }) - .optional() - .allow(''), + // 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: [ + violationSchema, + Joi.array() + .items(violationSchema) + .required(), + ], + }), }), }).required(), }).required() -module.exports = class Sensiolabs extends BaseXmlService { +const keywords = ['sensiolabs'] + +module.exports = class SymfonyInsight extends BaseXmlService { static render({ metric, status, @@ -65,13 +71,13 @@ module.exports = class Sensiolabs extends BaseXmlService { let color, message = grade if (grade === 'platinum') { - color = 'brightgreen' + color = '#E5E4E2' } else if (grade === 'gold') { - color = 'yellow' + color = '#EBC760' } else if (grade === 'silver') { - color = 'lightgrey' + color = '#C0C0C0' } else if (grade === 'bronze') { - color = 'orange' + color = '#C88F6A' } else { message = 'no medal' color = 'red' @@ -92,7 +98,7 @@ module.exports = class Sensiolabs extends BaseXmlService { }) { if (numViolations === 0) { return { - message: '0 violations', + message: '0', color: 'brightgreen', } } @@ -112,7 +118,7 @@ module.exports = class Sensiolabs extends BaseXmlService { color = 'orange' } if (numCriticalViolations > 0) { - violationSummary.unshift(`${numCriticalViolations} major`) + violationSummary.unshift(`${numCriticalViolations} critical`) color = 'red' } @@ -128,9 +134,13 @@ module.exports = class Sensiolabs extends BaseXmlService { static get route() { return { - // base: '(symfony|sensiolabs)/i', - // pattern: ':metric(grade|violations)?/:projectUuid', - pattern: '(symfony|sensiolabs)/i/:metric(grade|violations)?/:projectUuid', + 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'], } } @@ -143,30 +153,32 @@ module.exports = class Sensiolabs extends BaseXmlService { static get examples() { return [ { - title: 'SensioLabs Insight Grade', - pattern: 'grade/:projectUuid', + title: 'SymfonyInsight Grade', + pattern: 'symfony/i/grade/:projectUuid', namedParams: { projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87', }, staticPreview: this.renderGradeBadge({ grade: 'bronze', }), + keywords, }, { - title: 'SensioLabs Insight Violations', - pattern: 'violations/:projectUuid', + title: 'SymfonyInsight Violations', + pattern: 'symfony/i/violations/:projectUuid', namedParams: { projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87', }, staticPreview: this.renderGradeBadge({ numViolations: 0, }), + keywords, }, ] } async fetch({ projectUuid }) { - const url = `https://insight.sensiolabs.com/api/projects/${projectUuid}` + const url = `https://insight.symfony.com/api/projects/${projectUuid}` const options = { headers: { Accept: 'application/vnd.com.sensiolabs.insight+xml', @@ -204,9 +216,16 @@ module.exports = class Sensiolabs extends BaseXmlService { let numInfoViolations = 0 const violationContainer = lastAnalysis.violations - - if (violationContainer && violationContainer.violations) { - const violations = violationContainer.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') { diff --git a/services/symfony/symfony-insight.tester.js b/services/symfony/symfony-insight.tester.js new file mode 100644 index 0000000000000..4c251995018fa --- /dev/null +++ b/services/symfony/symfony-insight.tester.js @@ -0,0 +1,304 @@ +'use strict' + +const Joi = require('joi') +const { colorScheme } = require('../test-helpers') +const t = (module.exports = require('../create-service-tester')()) +const { withRegex } = require('../test-validators') + +const { + runningMockResponse, + platinumMockResponse, + goldMockResponse, + silverMockResponse, + bronzeMockResponse, + noMedalMockResponse, + mockSymfonyUser, + mockSymfonyToken, + mockSymfonyInsightCreds, + restore, + tokenExists, + logTokenWarning, + criticalViolation, + majorViolation, + minorViolation, + infoViolation, + multipleViolations, +} = require('./symfony-test-helpers') + +const gradeRegex = /platinum|gold|silver|bronze|no medal/ +const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87' + +t.create('live: valid project grade') + .before(logTokenWarning) + .get(`/symfony/i/grade/${sampleProjectUuid}.json`) + .timeout(10000) + .interceptIf(!tokenExists, nock => + nock('https://insight.smyfony.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, platinumMockResponse) + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'checks', + value: withRegex(gradeRegex), + }) + ) + +t.create('live: valid project violations') + .before(logTokenWarning) + .get(`/symfony/i/violations/${sampleProjectUuid}.json`) + .timeout(10000) + .interceptIf(!tokenExists, nock => + nock('https://insight.smyfony.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, platinumMockResponse) + ) + .expectJSONTypes( + Joi.object().keys({ + name: 'checks', + 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/ + ), + }) + ) + +t.create('live: nonexistent project') + .before(logTokenWarning) + .get('/symfony/i/grade/45afb680-d4e6-4e66-93ea-bcfa79eb8a88.json') + .interceptIf(!tokenExists, nock => + nock('https://insight.symfony.com/api/projects') + .get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88') + .reply(404) + ) + .expectJSON({ + name: 'checks', + value: 'project not found', + }) + +t.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: 'checks', + value: 'project not found', + }) + +t.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: 'checks', + value: 'not authorized to access project', + }) + +t.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: 'checks', + value: 'pending', + colorB: colorScheme.grey, + }) + +t.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: 'checks', + value: 'platinum', + colorB: '#E5E4E2', + }) + +t.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: 'checks', + value: 'gold', + colorB: '#EBC760', + }) + +t.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: 'checks', + value: 'silver', + colorB: '#C0C0C0', + }) + +t.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: 'checks', + value: 'bronze', + colorB: '#C88F6A', + }) + +t.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: 'checks', + value: 'no medal', + colorB: colorScheme.red, + }) + +t.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: 'checks', + value: '0', + colorB: colorScheme.brightgreen, + }) + +t.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: 'checks', + value: '1 critical', + colorB: colorScheme.red, + }) + +t.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: 'checks', + value: '1 major', + colorB: colorScheme.orange, + }) + +t.create('minor violations') + .get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.symfony.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, minorViolation) + ) + .expectJSON({ + name: 'checks', + value: '1 minor', + colorB: colorScheme.yellow, + }) + +t.create('info violations') + .get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.symfony.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, infoViolation) + ) + .expectJSON({ + name: 'checks', + value: '1 info', + colorB: colorScheme.yellowgreen, + }) + +t.create('multiple violations grade') + .get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.symfony.com/api/projects') + .get(`/${sampleProjectUuid}`) + .reply(200, multipleViolations) + ) + .expectJSON({ + name: 'checks', + value: '1 critical, 1 info', + colorB: colorScheme.red, + }) + +t.create('auth') + .before(mockSymfonyInsightCreds) + .get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`) + .intercept(nock => + nock('https://insight.symfony.com/api/projects') + .get(`/${sampleProjectUuid}`) + // 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: mockSymfonyUser, + pass: mockSymfonyToken, + }) + .reply(200, bronzeMockResponse) + ) + .expectJSON({ + name: 'checks', + value: 'bronze', + colorB: '#C88F6A', + }) + .finally(restore) + +// These tests ensure that the legacy badge path (/sensiolabs/i/projectUuid) still works +t.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: 'checks', + value: 'pending', + colorB: colorScheme.grey, + }) + +t.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: 'checks', + value: 'platinum', + colorB: '#E5E4E2', + }) diff --git a/services/symfony/symfony-test-helpers.js b/services/symfony/symfony-test-helpers.js new file mode 100644 index 0000000000000..693c34e1133b3 --- /dev/null +++ b/services/symfony/symfony-test-helpers.js @@ -0,0 +1,116 @@ +'use strict' + +const sinon = require('sinon') +const serverSecrets = require('../../lib/server-secrets') + +function createMockResponse({ status = 'finished', grade, violations }) { + let response = ` + + + + ${grade ? `` : ''}` + if (violations) { + response = `${response}` + violations.forEach(v => { + response = `${response}` + }) + response = `${response}` + } + return `${response}` +} + +const runningMockResponse = createMockResponse({ + status: 'running', +}) +const platinumMockResponse = createMockResponse({ + grade: 'platinum', +}) +const goldMockResponse = createMockResponse({ + grade: 'gold', +}) +const silverMockResponse = createMockResponse({ + grade: 'silver', +}) +const bronzeMockResponse = createMockResponse({ + grade: 'bronze', +}) +const noMedalMockResponse = createMockResponse({ + grade: 'none', +}) +const criticalViolation = createMockResponse({ + violations: [ + { + severity: 'critical', + }, + ], +}) +const majorViolation = createMockResponse({ + violations: [ + { + severity: 'major', + }, + ], +}) +const minorViolation = createMockResponse({ + violations: [ + { + severity: 'minor', + }, + ], +}) +const infoViolation = createMockResponse({ + violations: [ + { + severity: 'info', + }, + ], +}) +const multipleViolations = createMockResponse({ + violations: [ + { + severity: 'info', + }, + { + severity: 'critical', + }, + ], +}) + +const mockSymfonyUser = 'admin' +const mockSymfonyToken = 'password' + +function mockSymfonyInsightCreds() { + serverSecrets['sl_insight_userUuid'] = undefined + serverSecrets['sl_insight_apiToken'] = undefined + sinon.stub(serverSecrets, 'sl_insight_userUuid').value(mockSymfonyUser) + sinon.stub(serverSecrets, 'sl_insight_apiToken').value(mockSymfonyToken) +} + +const tokenExists = serverSecrets.sl_insight_userUuid +function logTokenWarning() { + if (!tokenExists) { + console.warn( + 'No token provided, this test will mock Symfony Insight API responses.' + ) + } +} + +module.exports = { + runningMockResponse, + platinumMockResponse, + goldMockResponse, + silverMockResponse, + bronzeMockResponse, + noMedalMockResponse, + mockSymfonyUser, + mockSymfonyToken, + mockSymfonyInsightCreds, + restore: sinon.restore, + tokenExists, + logTokenWarning, + criticalViolation, + majorViolation, + minorViolation, + infoViolation, + multipleViolations, +} From 8b424585f2807091cc67db5f6f787684311362f6 Mon Sep 17 00:00:00 2001 From: calebcartwright Date: Mon, 24 Dec 2018 00:33:59 -0500 Subject: [PATCH 5/9] fixed typos in SymfonyInsight tests --- services/symfony/symfony-insight.tester.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/symfony/symfony-insight.tester.js b/services/symfony/symfony-insight.tester.js index 4c251995018fa..53127f6f1c169 100644 --- a/services/symfony/symfony-insight.tester.js +++ b/services/symfony/symfony-insight.tester.js @@ -32,8 +32,8 @@ t.create('live: valid project grade') .before(logTokenWarning) .get(`/symfony/i/grade/${sampleProjectUuid}.json`) .timeout(10000) - .interceptIf(!tokenExists, nock => - nock('https://insight.smyfony.com/api/projects') + .interceptIf(tokenExists, nock => + nock('https://insight.symfony.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, platinumMockResponse) ) @@ -48,10 +48,10 @@ t.create('live: valid project violations') .before(logTokenWarning) .get(`/symfony/i/violations/${sampleProjectUuid}.json`) .timeout(10000) - .interceptIf(!tokenExists, nock => - nock('https://insight.smyfony.com/api/projects') + .interceptIf(tokenExists, nock => + nock('https://insight.symfony.com/api/projects') .get(`/${sampleProjectUuid}`) - .reply(200, platinumMockResponse) + .reply(200, multipleViolations) ) .expectJSONTypes( Joi.object().keys({ From afc880e90c1a899f79eb08e87db34d5f79803cf6 Mon Sep 17 00:00:00 2001 From: calebcartwright Date: Mon, 24 Dec 2018 00:36:51 -0500 Subject: [PATCH 6/9] fixed tests for symfonyinsight --- services/symfony/symfony-insight.tester.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/symfony/symfony-insight.tester.js b/services/symfony/symfony-insight.tester.js index 53127f6f1c169..e2845d950b453 100644 --- a/services/symfony/symfony-insight.tester.js +++ b/services/symfony/symfony-insight.tester.js @@ -32,7 +32,7 @@ t.create('live: valid project grade') .before(logTokenWarning) .get(`/symfony/i/grade/${sampleProjectUuid}.json`) .timeout(10000) - .interceptIf(tokenExists, nock => + .interceptIf(!tokenExists, nock => nock('https://insight.symfony.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, platinumMockResponse) @@ -48,7 +48,7 @@ t.create('live: valid project violations') .before(logTokenWarning) .get(`/symfony/i/violations/${sampleProjectUuid}.json`) .timeout(10000) - .interceptIf(tokenExists, nock => + .interceptIf(!tokenExists, nock => nock('https://insight.symfony.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, multipleViolations) From 19658589b25d2327b51bd20b8d50b596fb281bc6 Mon Sep 17 00:00:00 2001 From: calebcartwright Date: Tue, 1 Jan 2019 22:10:38 -0600 Subject: [PATCH 7/9] feat: minor label and color updates for SymfonyInsight badges --- services/symfony/symfony-insight.service.js | 46 ++++++++++-------- services/symfony/symfony-insight.tester.js | 53 +++++++++++---------- 2 files changed, 54 insertions(+), 45 deletions(-) diff --git a/services/symfony/symfony-insight.service.js b/services/symfony/symfony-insight.service.js index 900ae62c33fef..b2385b4093b08 100644 --- a/services/symfony/symfony-insight.service.js +++ b/services/symfony/symfony-insight.service.js @@ -5,30 +5,30 @@ const BaseXmlService = require('../base-xml') const serverSecrets = require('../../lib/server-secrets') const violationSchema = Joi.object({ - severity: Joi.string() - .regex(/info|minor|major|critical/) - .required(), + severity: Joi.equal('info', 'minor', 'major', 'critical').required(), }).required() const schema = Joi.object({ project: Joi.object({ 'last-analysis': Joi.object({ - status: Joi.string() - .regex(/ordered|running|measured|analyzed|finished/) - .required(), - grade: Joi.string().regex(/platinum|gold|silver|bronze|none/), + 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: [ - violationSchema, - Joi.array() - .items(violationSchema) - .required(), - ], + violation: Joi.array() + .items(violationSchema) + .single() + .required(), }), }), }).required(), @@ -49,8 +49,9 @@ module.exports = class SymfonyInsight extends BaseXmlService { }) { if (status !== 'finished') { return { + label: metric, message: 'pending', - color: 'grey', + color: 'lightgrey', } } @@ -84,6 +85,7 @@ module.exports = class SymfonyInsight extends BaseXmlService { } return { + label: 'grade', message, color, } @@ -98,6 +100,7 @@ module.exports = class SymfonyInsight extends BaseXmlService { }) { if (numViolations === 0) { return { + label: 'violations', message: '0', color: 'brightgreen', } @@ -123,11 +126,18 @@ module.exports = class SymfonyInsight extends BaseXmlService { } return { + label: 'violations', message: violationSummary.join(', '), color, } } + static get defaultBadgeData() { + return { + label: 'symfony insight', + } + } + static get category() { return 'quality' } @@ -144,12 +154,6 @@ module.exports = class SymfonyInsight extends BaseXmlService { } } - static get defaultBadgeData() { - return { - label: 'checks', - } - } - static get examples() { return [ { @@ -169,7 +173,7 @@ module.exports = class SymfonyInsight extends BaseXmlService { namedParams: { projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87', }, - staticPreview: this.renderGradeBadge({ + staticPreview: this.renderViolationsBadge({ numViolations: 0, }), keywords, diff --git a/services/symfony/symfony-insight.tester.js b/services/symfony/symfony-insight.tester.js index e2845d950b453..1d96a4e86d34a 100644 --- a/services/symfony/symfony-insight.tester.js +++ b/services/symfony/symfony-insight.tester.js @@ -25,7 +25,6 @@ const { multipleViolations, } = require('./symfony-test-helpers') -const gradeRegex = /platinum|gold|silver|bronze|no medal/ const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87' t.create('live: valid project grade') @@ -39,8 +38,14 @@ t.create('live: valid project grade') ) .expectJSONTypes( Joi.object().keys({ - name: 'checks', - value: withRegex(gradeRegex), + name: 'grade', + value: Joi.equal( + 'platinum', + 'gold', + 'silver', + 'bronze', + 'no medal' + ).required(), }) ) @@ -55,7 +60,7 @@ t.create('live: valid project violations') ) .expectJSONTypes( Joi.object().keys({ - name: 'checks', + 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/ ), @@ -71,7 +76,7 @@ t.create('live: nonexistent project') .reply(404) ) .expectJSON({ - name: 'checks', + name: 'symfony insight', value: 'project not found', }) @@ -83,7 +88,7 @@ t.create('404 project not found grade') .reply(404) ) .expectJSON({ - name: 'checks', + name: 'symfony insight', value: 'project not found', }) @@ -95,7 +100,7 @@ t.create('401 not authorized grade') .reply(401) ) .expectJSON({ - name: 'checks', + name: 'symfony insight', value: 'not authorized to access project', }) @@ -107,9 +112,9 @@ t.create('pending project grade') .reply(200, runningMockResponse) ) .expectJSON({ - name: 'checks', + name: 'grade', value: 'pending', - colorB: colorScheme.grey, + colorB: colorScheme.lightgrey, }) t.create('platinum grade') @@ -120,7 +125,7 @@ t.create('platinum grade') .reply(200, platinumMockResponse) ) .expectJSON({ - name: 'checks', + name: 'grade', value: 'platinum', colorB: '#E5E4E2', }) @@ -133,7 +138,7 @@ t.create('gold grade') .reply(200, goldMockResponse) ) .expectJSON({ - name: 'checks', + name: 'grade', value: 'gold', colorB: '#EBC760', }) @@ -146,7 +151,7 @@ t.create('silver grade') .reply(200, silverMockResponse) ) .expectJSON({ - name: 'checks', + name: 'grade', value: 'silver', colorB: '#C0C0C0', }) @@ -159,7 +164,7 @@ t.create('bronze grade') .reply(200, bronzeMockResponse) ) .expectJSON({ - name: 'checks', + name: 'grade', value: 'bronze', colorB: '#C88F6A', }) @@ -172,7 +177,7 @@ t.create('no medal grade') .reply(200, noMedalMockResponse) ) .expectJSON({ - name: 'checks', + name: 'grade', value: 'no medal', colorB: colorScheme.red, }) @@ -185,7 +190,7 @@ t.create('zero violations') .reply(200, goldMockResponse) ) .expectJSON({ - name: 'checks', + name: 'violations', value: '0', colorB: colorScheme.brightgreen, }) @@ -198,7 +203,7 @@ t.create('critical violations') .reply(200, criticalViolation) ) .expectJSON({ - name: 'checks', + name: 'violations', value: '1 critical', colorB: colorScheme.red, }) @@ -211,7 +216,7 @@ t.create('major violations') .reply(200, majorViolation) ) .expectJSON({ - name: 'checks', + name: 'violations', value: '1 major', colorB: colorScheme.orange, }) @@ -224,7 +229,7 @@ t.create('minor violations') .reply(200, minorViolation) ) .expectJSON({ - name: 'checks', + name: 'violations', value: '1 minor', colorB: colorScheme.yellow, }) @@ -237,7 +242,7 @@ t.create('info violations') .reply(200, infoViolation) ) .expectJSON({ - name: 'checks', + name: 'violations', value: '1 info', colorB: colorScheme.yellowgreen, }) @@ -250,7 +255,7 @@ t.create('multiple violations grade') .reply(200, multipleViolations) ) .expectJSON({ - name: 'checks', + name: 'violations', value: '1 critical, 1 info', colorB: colorScheme.red, }) @@ -270,7 +275,7 @@ t.create('auth') .reply(200, bronzeMockResponse) ) .expectJSON({ - name: 'checks', + name: 'grade', value: 'bronze', colorB: '#C88F6A', }) @@ -285,9 +290,9 @@ t.create('legacy path: pending project grade') .reply(200, runningMockResponse) ) .expectJSON({ - name: 'checks', + name: 'grade', value: 'pending', - colorB: colorScheme.grey, + colorB: colorScheme.lightgrey, }) t.create('legacy path: platinum grade') @@ -298,7 +303,7 @@ t.create('legacy path: platinum grade') .reply(200, platinumMockResponse) ) .expectJSON({ - name: 'checks', + name: 'grade', value: 'platinum', colorB: '#E5E4E2', }) From d36fd22de7189572709b9f41f89e3f03eebd7443 Mon Sep 17 00:00:00 2001 From: calebcartwright Date: Wed, 2 Jan 2019 19:03:52 -0600 Subject: [PATCH 8/9] feat: updated symfonyinsight and tests to handle no creds --- services/symfony/symfony-insight.service.js | 15 +++-- services/symfony/symfony-insight.tester.js | 63 ++++++++++++--------- services/symfony/symfony-test-helpers.js | 19 +++++-- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/services/symfony/symfony-insight.service.js b/services/symfony/symfony-insight.service.js index b2385b4093b08..a799ba021ee5f 100644 --- a/services/symfony/symfony-insight.service.js +++ b/services/symfony/symfony-insight.service.js @@ -3,6 +3,7 @@ const Joi = require('joi') const BaseXmlService = require('../base-xml') const serverSecrets = require('../../lib/server-secrets') +const { Inaccessible } = require('../errors') const violationSchema = Joi.object({ severity: Joi.equal('info', 'minor', 'major', 'critical').required(), @@ -189,11 +190,15 @@ module.exports = class SymfonyInsight extends BaseXmlService { }, } - if (serverSecrets && serverSecrets.sl_insight_userUuid) { - options.auth = { - user: serverSecrets.sl_insight_userUuid, - pass: serverSecrets.sl_insight_apiToken, - } + if (!serverSecrets.sl_insight_userUuid) { + 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({ diff --git a/services/symfony/symfony-insight.tester.js b/services/symfony/symfony-insight.tester.js index 1d96a4e86d34a..d5d3971a78bbc 100644 --- a/services/symfony/symfony-insight.tester.js +++ b/services/symfony/symfony-insight.tester.js @@ -16,8 +16,8 @@ const { mockSymfonyToken, mockSymfonyInsightCreds, restore, - tokenExists, - logTokenWarning, + realTokenExists, + prepLiveTest, criticalViolation, majorViolation, minorViolation, @@ -27,11 +27,19 @@ const { const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87' +beforeEach(function() { + mockSymfonyInsightCreds() +}) + +afterEach(function() { + restore() +}) + t.create('live: valid project grade') - .before(logTokenWarning) + .before(prepLiveTest) .get(`/symfony/i/grade/${sampleProjectUuid}.json`) - .timeout(10000) - .interceptIf(!tokenExists, nock => + .timeout(15000) + .interceptIf(!realTokenExists, nock => nock('https://insight.symfony.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, platinumMockResponse) @@ -50,10 +58,10 @@ t.create('live: valid project grade') ) t.create('live: valid project violations') - .before(logTokenWarning) + .before(prepLiveTest) .get(`/symfony/i/violations/${sampleProjectUuid}.json`) - .timeout(10000) - .interceptIf(!tokenExists, nock => + .timeout(15000) + .interceptIf(!realTokenExists, nock => nock('https://insight.symfony.com/api/projects') .get(`/${sampleProjectUuid}`) .reply(200, multipleViolations) @@ -68,9 +76,9 @@ t.create('live: valid project violations') ) t.create('live: nonexistent project') - .before(logTokenWarning) + .before(prepLiveTest) .get('/symfony/i/grade/45afb680-d4e6-4e66-93ea-bcfa79eb8a88.json') - .interceptIf(!tokenExists, nock => + .interceptIf(!realTokenExists, nock => nock('https://insight.symfony.com/api/projects') .get('/45afb680-d4e6-4e66-93ea-bcfa79eb8a88') .reply(404) @@ -226,6 +234,10 @@ t.create('minor violations') .intercept(nock => nock('https://insight.symfony.com/api/projects') .get(`/${sampleProjectUuid}`) + .basicAuth({ + user: mockSymfonyUser, + pass: mockSymfonyToken, + }) .reply(200, minorViolation) ) .expectJSON({ @@ -239,6 +251,10 @@ t.create('info violations') .intercept(nock => nock('https://insight.symfony.com/api/projects') .get(`/${sampleProjectUuid}`) + .basicAuth({ + user: mockSymfonyUser, + pass: mockSymfonyToken, + }) .reply(200, infoViolation) ) .expectJSON({ @@ -252,6 +268,10 @@ t.create('multiple violations grade') .intercept(nock => nock('https://insight.symfony.com/api/projects') .get(`/${sampleProjectUuid}`) + .basicAuth({ + user: mockSymfonyUser, + pass: mockSymfonyToken, + }) .reply(200, multipleViolations) ) .expectJSON({ @@ -260,26 +280,13 @@ t.create('multiple violations grade') colorB: colorScheme.red, }) -t.create('auth') - .before(mockSymfonyInsightCreds) - .get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`) - .intercept(nock => - nock('https://insight.symfony.com/api/projects') - .get(`/${sampleProjectUuid}`) - // 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: mockSymfonyUser, - pass: mockSymfonyToken, - }) - .reply(200, bronzeMockResponse) - ) +t.create('auth missing') + .before(restore) + .get(`/symfony/i/grade/${sampleProjectUuid}.json`) .expectJSON({ - name: 'grade', - value: 'bronze', - colorB: '#C88F6A', + name: 'symfony insight', + value: 'required API tokens not found in config', }) - .finally(restore) // These tests ensure that the legacy badge path (/sensiolabs/i/projectUuid) still works t.create('legacy path: pending project grade') diff --git a/services/symfony/symfony-test-helpers.js b/services/symfony/symfony-test-helpers.js index 693c34e1133b3..a1a4567d96c7e 100644 --- a/services/symfony/symfony-test-helpers.js +++ b/services/symfony/symfony-test-helpers.js @@ -86,12 +86,21 @@ function mockSymfonyInsightCreds() { sinon.stub(serverSecrets, 'sl_insight_apiToken').value(mockSymfonyToken) } -const tokenExists = serverSecrets.sl_insight_userUuid -function logTokenWarning() { - if (!tokenExists) { +const originalUuid = serverSecrets.sl_insight_userUuid +const originalApiToken = serverSecrets.sl_insight_apiToken + +function prepLiveTest() { + // Since the service implementation will throw an error if the creds + // are missing, there is a beforeEach hook that ensures there's mock creds + // used for each test. This will use the "real" creds if the exist, otherwise + // it will use the same stubbed creds as all the mocked tests. + if (!originalUuid) { console.warn( 'No token provided, this test will mock Symfony Insight API responses.' ) + } else { + serverSecrets.sl_insight_userUuid = originalUuid + serverSecrets.sl_insight_apiToken = originalApiToken } } @@ -106,8 +115,8 @@ module.exports = { mockSymfonyToken, mockSymfonyInsightCreds, restore: sinon.restore, - tokenExists, - logTokenWarning, + realTokenExists: originalUuid, + prepLiveTest, criticalViolation, majorViolation, minorViolation, From 29873eed6fc63106337483e18258ef5e2675eda8 Mon Sep 17 00:00:00 2001 From: calebcartwright Date: Thu, 3 Jan 2019 12:54:37 -0600 Subject: [PATCH 9/9] feat: updated Symfony service to throw error when required tokens missing in config --- services/symfony/symfony-insight.service.js | 5 +- services/symfony/symfony-insight.tester.js | 58 +++++++++++---------- services/symfony/symfony-test-helpers.js | 29 +++++++---- 3 files changed, 53 insertions(+), 39 deletions(-) diff --git a/services/symfony/symfony-insight.service.js b/services/symfony/symfony-insight.service.js index a799ba021ee5f..280ca692f871f 100644 --- a/services/symfony/symfony-insight.service.js +++ b/services/symfony/symfony-insight.service.js @@ -190,7 +190,10 @@ module.exports = class SymfonyInsight extends BaseXmlService { }, } - if (!serverSecrets.sl_insight_userUuid) { + if ( + !serverSecrets.sl_insight_userUuid || + !serverSecrets.sl_insight_apiToken + ) { throw new Inaccessible({ prettyMessage: 'required API tokens not found in config', }) diff --git a/services/symfony/symfony-insight.tester.js b/services/symfony/symfony-insight.tester.js index d5d3971a78bbc..fb63e087a9fa6 100644 --- a/services/symfony/symfony-insight.tester.js +++ b/services/symfony/symfony-insight.tester.js @@ -15,6 +15,7 @@ const { mockSymfonyUser, mockSymfonyToken, mockSymfonyInsightCreds, + setSymfonyInsightCredsToFalsy, restore, realTokenExists, prepLiveTest, @@ -27,15 +28,16 @@ const { const sampleProjectUuid = '45afb680-d4e6-4e66-93ea-bcfa79eb8a87' -beforeEach(function() { - mockSymfonyInsightCreds() -}) +function create(title, { withMockCreds = true } = { withMockCreds: true }) { + const result = t.create(title) + if (withMockCreds) { + result.before(mockSymfonyInsightCreds) + result.finally(restore) + } + return result +} -afterEach(function() { - restore() -}) - -t.create('live: valid project grade') +create('live: valid project grade', { withMockCreds: false }) .before(prepLiveTest) .get(`/symfony/i/grade/${sampleProjectUuid}.json`) .timeout(15000) @@ -57,7 +59,7 @@ t.create('live: valid project grade') }) ) -t.create('live: valid project violations') +create('live: valid project violations', { withMockCreds: false }) .before(prepLiveTest) .get(`/symfony/i/violations/${sampleProjectUuid}.json`) .timeout(15000) @@ -75,7 +77,7 @@ t.create('live: valid project violations') }) ) -t.create('live: nonexistent project') +create('live: nonexistent project', { withMockCreds: false }) .before(prepLiveTest) .get('/symfony/i/grade/45afb680-d4e6-4e66-93ea-bcfa79eb8a88.json') .interceptIf(!realTokenExists, nock => @@ -88,7 +90,7 @@ t.create('live: nonexistent project') value: 'project not found', }) -t.create('404 project not found grade') +create('404 project not found grade') .get(`/symfony/i/grade/${sampleProjectUuid}.json`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -100,7 +102,7 @@ t.create('404 project not found grade') value: 'project not found', }) -t.create('401 not authorized grade') +create('401 not authorized grade') .get(`/symfony/i/grade/${sampleProjectUuid}.json`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -112,7 +114,7 @@ t.create('401 not authorized grade') value: 'not authorized to access project', }) -t.create('pending project grade') +create('pending project grade') .get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -125,7 +127,7 @@ t.create('pending project grade') colorB: colorScheme.lightgrey, }) -t.create('platinum grade') +create('platinum grade') .get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -138,7 +140,7 @@ t.create('platinum grade') colorB: '#E5E4E2', }) -t.create('gold grade') +create('gold grade') .get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -151,7 +153,7 @@ t.create('gold grade') colorB: '#EBC760', }) -t.create('silver grade') +create('silver grade') .get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -164,7 +166,7 @@ t.create('silver grade') colorB: '#C0C0C0', }) -t.create('bronze grade') +create('bronze grade') .get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -177,7 +179,7 @@ t.create('bronze grade') colorB: '#C88F6A', }) -t.create('no medal grade') +create('no medal grade') .get(`/symfony/i/grade/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -190,7 +192,7 @@ t.create('no medal grade') colorB: colorScheme.red, }) -t.create('zero violations') +create('zero violations') .get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -203,7 +205,7 @@ t.create('zero violations') colorB: colorScheme.brightgreen, }) -t.create('critical violations') +create('critical violations') .get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -216,7 +218,7 @@ t.create('critical violations') colorB: colorScheme.red, }) -t.create('major violations') +create('major violations') .get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -229,7 +231,7 @@ t.create('major violations') colorB: colorScheme.orange, }) -t.create('minor violations') +create('minor violations') .get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -246,7 +248,7 @@ t.create('minor violations') colorB: colorScheme.yellow, }) -t.create('info violations') +create('info violations') .get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -263,7 +265,7 @@ t.create('info violations') colorB: colorScheme.yellowgreen, }) -t.create('multiple violations grade') +create('multiple violations grade') .get(`/symfony/i/violations/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -280,8 +282,8 @@ t.create('multiple violations grade') colorB: colorScheme.red, }) -t.create('auth missing') - .before(restore) +create('auth missing', { withMockCreds: false }) + .before(setSymfonyInsightCredsToFalsy) .get(`/symfony/i/grade/${sampleProjectUuid}.json`) .expectJSON({ name: 'symfony insight', @@ -289,7 +291,7 @@ t.create('auth missing') }) // These tests ensure that the legacy badge path (/sensiolabs/i/projectUuid) still works -t.create('legacy path: pending project grade') +create('legacy path: pending project grade') .get(`/sensiolabs/i/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') @@ -302,7 +304,7 @@ t.create('legacy path: pending project grade') colorB: colorScheme.lightgrey, }) -t.create('legacy path: platinum grade') +create('legacy path: platinum grade') .get(`/sensiolabs/i/${sampleProjectUuid}.json?style=_shields_test`) .intercept(nock => nock('https://insight.symfony.com/api/projects') diff --git a/services/symfony/symfony-test-helpers.js b/services/symfony/symfony-test-helpers.js index a1a4567d96c7e..43d27dd267b88 100644 --- a/services/symfony/symfony-test-helpers.js +++ b/services/symfony/symfony-test-helpers.js @@ -78,29 +78,37 @@ const multipleViolations = createMockResponse({ const mockSymfonyUser = 'admin' const mockSymfonyToken = 'password' +const originalUuid = serverSecrets.sl_insight_userUuid +const originalApiToken = serverSecrets.sl_insight_apiToken -function mockSymfonyInsightCreds() { +function setSymfonyInsightCredsToFalsy() { serverSecrets['sl_insight_userUuid'] = undefined serverSecrets['sl_insight_apiToken'] = undefined +} + +function mockSymfonyInsightCreds() { + // ensure that the fields exists before attempting to stub + setSymfonyInsightCredsToFalsy() sinon.stub(serverSecrets, 'sl_insight_userUuid').value(mockSymfonyUser) sinon.stub(serverSecrets, 'sl_insight_apiToken').value(mockSymfonyToken) } -const originalUuid = serverSecrets.sl_insight_userUuid -const originalApiToken = serverSecrets.sl_insight_apiToken +function restore() { + sinon.restore() + serverSecrets['sl_insight_userUuid'] = originalUuid + serverSecrets['sl_insight_apiToken'] = originalApiToken +} function prepLiveTest() { // Since the service implementation will throw an error if the creds - // are missing, there is a beforeEach hook that ensures there's mock creds - // used for each test. This will use the "real" creds if the exist, otherwise - // it will use the same stubbed creds as all the mocked tests. + // are missing, we need to ensure that creds are available for each test. + // In the case of the live tests we want to use the "real" creds if they + // exist otherwise we need to use the same stubbed creds as all the mocked tests. if (!originalUuid) { console.warn( 'No token provided, this test will mock Symfony Insight API responses.' ) - } else { - serverSecrets.sl_insight_userUuid = originalUuid - serverSecrets.sl_insight_apiToken = originalApiToken + mockSymfonyInsightCreds() } } @@ -114,7 +122,8 @@ module.exports = { mockSymfonyUser, mockSymfonyToken, mockSymfonyInsightCreds, - restore: sinon.restore, + setSymfonyInsightCredsToFalsy, + restore, realTokenExists: originalUuid, prepLiveTest, criticalViolation,