From cbb263b10262573a072f2a8e233723460fc6ce90 Mon Sep 17 00:00:00 2001 From: chris48s Date: Sat, 3 Feb 2018 16:11:28 +0000 Subject: [PATCH] service tests for [cocoapods] (#1472) * service tests for [cocoapods] * Add tests for CocoaPods Endpoints Bug fixes: * Call `metrics.cocoapods.org` API endpoints over HTTPs * Show correct left-side badge text on error in version/platform/license badges * Handle null doc coverage gracefully * Add handling for 'not found' responses * drop last param to checkErrorResponse * remove redundant line * set badge label using more terse notation * specify allowed platform values --- server.js | 29 +++--- service-tests/cocoapods.js | 204 +++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 16 deletions(-) create mode 100644 service-tests/cocoapods.js diff --git a/server.js b/server.js index 407f3f0000c33..ae6d2429aad1e 100644 --- a/server.js +++ b/server.js @@ -3383,11 +3383,11 @@ cache(function(data, match, sendBadge, request) { var spec = match[2]; // eg, AFNetworking var format = match[3]; var apiUrl = 'https://trunk.cocoapods.org/api/v1/pods/' + spec + '/specs/latest'; - var badgeData = getBadgeData('pod', data); + const typeToLabel = {'v' : 'pod', 'p': 'platform', 'l': 'license'}; + const badgeData = getBadgeData(typeToLabel[type], data); badgeData.colorscheme = null; request(apiUrl, function(err, res, buffer) { - if (err != null) { - badgeData.text[1] = 'inaccessible'; + if (checkErrorResponse(badgeData, err, res)) { sendBadge(format, badgeData); return; } @@ -3407,11 +3407,9 @@ cache(function(data, match, sendBadge, request) { badgeData.text[1] = versionText(version); badgeData.colorscheme = versionColor(version); } else if (type === 'p') { - badgeData.text[0] = getLabel('platform', data); badgeData.text[1] = platforms; badgeData.colorB = '#989898'; } else if (type === 'l') { - badgeData.text[0] = getLabel('license', data); badgeData.text[1] = license; badgeData.colorB = '#373737'; } @@ -3429,19 +3427,20 @@ camp.route(/^\/cocoapods\/metrics\/doc-percent\/(.*)\.(svg|png|gif|jpg|json)$/, cache(function(data, match, sendBadge, request) { var spec = match[1]; // eg, AFNetworking var format = match[2]; - var apiUrl = 'http://metrics.cocoapods.org/api/v1/pods/' + spec; - var badgeData = getBadgeData('pod', data); + var apiUrl = 'https://metrics.cocoapods.org/api/v1/pods/' + spec; + var badgeData = getBadgeData('docs', data); request(apiUrl, function(err, res, buffer) { - if (err != null) { - badgeData.text[1] = 'inaccessible'; + if (checkErrorResponse(badgeData, err, res)) { sendBadge(format, badgeData); return; } try { var parsedData = JSON.parse(buffer); var percentage = parsedData.cocoadocs.doc_percent; + if (percentage == null) { + percentage = 0; + } badgeData.colorscheme = coveragePercentageColor(percentage); - badgeData.text[0] = getLabel('docs', data); badgeData.text[1] = percentage + '%'; sendBadge(format, badgeData); } catch(e) { @@ -3457,11 +3456,10 @@ cache(function(data, match, sendBadge, request) { var info = match[1]; // One of these: "dm", "dw", "dt" var spec = match[2]; // eg, AFNetworking var format = match[3]; - var apiUrl = 'http://metrics.cocoapods.org/api/v1/pods/' + spec; + var apiUrl = 'https://metrics.cocoapods.org/api/v1/pods/' + spec; var badgeData = getBadgeData('downloads', data); request(apiUrl, function(err, res, buffer) { - if (err != null) { - badgeData.text[1] = 'inaccessible'; + if (checkErrorResponse(badgeData, err, res)) { sendBadge(format, badgeData); return; } @@ -3497,11 +3495,10 @@ cache(function(data, match, sendBadge, request) { var info = match[1]; // One of these: "aw", "at" var spec = match[2]; // eg, AFNetworking var format = match[3]; - var apiUrl = 'http://metrics.cocoapods.org/api/v1/pods/' + spec; + var apiUrl = 'https://metrics.cocoapods.org/api/v1/pods/' + spec; var badgeData = getBadgeData('apps', data); request(apiUrl, function(err, res, buffer) { - if (err != null) { - badgeData.text[1] = 'inaccessible'; + if (checkErrorResponse(badgeData, err, res)) { sendBadge(format, badgeData); return; } diff --git a/service-tests/cocoapods.js b/service-tests/cocoapods.js new file mode 100644 index 0000000000000..ee251f5d86486 --- /dev/null +++ b/service-tests/cocoapods.js @@ -0,0 +1,204 @@ +'use strict'; + +const Joi = require('joi'); +const ServiceTester = require('./runner/service-tester'); + +const { + isMetric, + isMetricOverTimePeriod, + isIntegerPercentage, + isVPlusDottedVersionAtLeastOne, +} = require('./helpers/validators'); + +const isPlatform = Joi.string().regex(/^(osx|ios|tvos|watchos)( \| (osx|ios|tvos|watchos))*$/); + +const t = new ServiceTester({ id: 'cocoapods', title: 'Cocoa Pods' }); +module.exports = t; + + +// version endpoint + +t.create('version (valid)') + .get('/v/AFNetworking.json') + .expectJSONTypes(Joi.object().keys({ + name: 'pod', + value: isVPlusDottedVersionAtLeastOne + })); + +t.create('version (not found)') + .get('/v/not-a-package.json') + .expectJSON({name: 'pod', value: 'not found'}); + +t.create('version (connection error)') + .get('/v/AFNetworking.json') + .networkOff() + .expectJSON({name: 'pod', value: 'inaccessible'}); + +t.create('version (unexpected response)') + .get('/v/AFNetworking.json') + .intercept(nock => nock('https://trunk.cocoapods.org') + .get('/api/v1/pods/AFNetworking/specs/latest') + .reply(200, "{{{{{invalid json}}") + ) + .expectJSON({name: 'pod', value: 'invalid'}); + + +// platform endpoint + +t.create('platform (valid)') + .get('/p/AFNetworking.json') + .expectJSONTypes(Joi.object().keys({ + name: 'platform', + value: isPlatform + })); + +t.create('platform (not found)') + .get('/p/not-a-package.json') + .expectJSON({name: 'platform', value: 'not found'}); + +t.create('platform (connection error)') + .get('/p/AFNetworking.json') + .networkOff() + .expectJSON({name: 'platform', value: 'inaccessible'}); + +t.create('platform (unexpected response)') + .get('/p/AFNetworking.json') + .intercept(nock => nock('https://trunk.cocoapods.org') + .get('/api/v1/pods/AFNetworking/specs/latest') + .reply(200, "{{{{{invalid json}}") + ) + .expectJSON({name: 'platform', value: 'invalid'}); + + +// license endpoint + +t.create('license (valid)') + .get('/l/AFNetworking.json') + .expectJSON({name: 'license', value: 'MIT'}); + +t.create('license (not found)') + .get('/l/not-a-package.json') + .expectJSON({name: 'license', value: 'not found'}); + +t.create('license (connection error)') + .get('/l/AFNetworking.json') + .networkOff() + .expectJSON({name: 'license', value: 'inaccessible'}); + +t.create('license (unexpected response)') + .get('/l/AFNetworking.json') + .intercept(nock => nock('https://trunk.cocoapods.org') + .get('/api/v1/pods/AFNetworking/specs/latest') + .reply(200, "{{{{{invalid json}}") + ) + .expectJSON({name: 'license', value: 'invalid'}); + + +// doc percent endpoint + +t.create('doc percent (valid)') + .get('/metrics/doc-percent/AFNetworking.json') + .expectJSONTypes(Joi.object().keys({ + name: 'docs', + value: isIntegerPercentage + })); + +t.create('doc percent (null)') + .get('/metrics/doc-percent/AFNetworking.json') + .intercept(nock => nock('https://metrics.cocoapods.org') + .get('/api/v1/pods/AFNetworking') + .reply(200, '{"cocoadocs": {"doc_percent": null}}') + ) + .expectJSON({name: 'docs', value: '0%'});; + +t.create('doc percent (not found)') + .get('/metrics/doc-percent/not-a-package.json') + .expectJSON({name: 'docs', value: 'not found'}); + +t.create('doc percent (connection error)') + .get('/metrics/doc-percent/AFNetworking.json') + .networkOff() + .expectJSON({name: 'docs', value: 'inaccessible'}); + +t.create('doc percent (unexpected response)') + .get('/metrics/doc-percent/AFNetworking.json') + .intercept(nock => nock('https://metrics.cocoapods.org') + .get('/api/v1/pods/AFNetworking') + .reply(200, "{{{{{invalid json}}") + ) + .expectJSON({name: 'docs', value: 'invalid'}); + + +// downloads endpoints + +t.create('downloads (valid, monthly)') + .get('/dm/AFNetworking.json') + .expectJSONTypes(Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod + })); + +t.create('downloads (valid, weekly)') + .get('/dw/AFNetworking.json') + .expectJSONTypes(Joi.object().keys({ + name: 'downloads', + value: isMetricOverTimePeriod + })); + +t.create('downloads (valid, total)') + .get('/dt/AFNetworking.json') + .expectJSONTypes(Joi.object().keys({ + name: 'downloads', + value: isMetric + })); + +t.create('downloads (not found)') + .get('/dt/not-a-package.json') + .expectJSON({name: 'downloads', value: 'not found'}); + +t.create('downloads (connection error)') + .get('/dt/AFNetworking.json') + .networkOff() + .expectJSON({name: 'downloads', value: 'inaccessible'}); + +t.create('downloads (unexpected response)') + .get('/dt/AFNetworking.json') + .intercept(nock => nock('https://metrics.cocoapods.org') + .get('/api/v1/pods/AFNetworking') + .reply(200, "{{{{{invalid json}}") + ) + .expectJSON({name: 'downloads', value: 'invalid'}); + + +// apps endpoints + +t.create('apps (valid, weekly)') + .get('/aw/AFNetworking.json') + .expectJSONTypes(Joi.object().keys({ + name: 'apps', + value: isMetricOverTimePeriod + })); + +t.create('apps (valid, total)') + .get('/at/AFNetworking.json') + .expectJSONTypes(Joi.object().keys({ + name: 'apps', + value: isMetric + })); + +t.create('apps (not found)') + .get('/at/not-a-package.json') + .expectJSON({name: 'apps', value: 'not found'}); + +t.create('apps (connection error)') + .get('/at/AFNetworking.json') + .networkOff() + .expectJSON({name: 'apps', value: 'inaccessible'}); + +t.create('apps (unexpected response)') + .get('/at/AFNetworking.json') + .intercept(nock => nock('https://metrics.cocoapods.org') + .get('/api/v1/pods/AFNetworking') + .reply(200, "{{{{{invalid json}}") + ) + .expectJSON({name: 'apps', value: 'invalid'});