Rewrite the DynamicJson badge (#2399)

This starts the rewrite of the dynamic badges. I've pulled into BaseService an initial version of the query param validation from #2325.

I've extended from BaseJsonService to avoid duplicating the deserialization logic, though it means there is a bit of duplicated code among the three dynamic services. The way to unravel this would be to move the logic from `_requestJson` and friends from the base classes into functions so DynamicJson can inherit from BaseDynamic. Would that be worth it?

This introduces a regression of #1446 for this badge.

Close #2345
This commit is contained in:
Paul Melnikow
2018-12-06 16:45:40 -05:00
committed by GitHub
parent b2d5d3ecca
commit 6a737b7b38
12 changed files with 180 additions and 65 deletions

View File

@@ -0,0 +1,39 @@
'use strict'
const Joi = require('joi')
const { optionalUrl } = require('../validators')
function createRoute(which) {
return {
base: `badge/dynamic/${which}`,
pattern: '',
queryParams: ['uri', 'url', 'query', 'prefix', 'suffix'],
}
}
const queryParamSchema = Joi.object({
url: optionalUrl.required(),
query: Joi.string().required(),
prefix: Joi.alternatives().try(Joi.string(), Joi.number()),
suffix: Joi.alternatives().try(Joi.string(), Joi.number()),
})
.rename('uri', 'url', { ignoreUndefined: true, override: true })
.required()
const errorMessages = {
404: 'resource not found',
}
function renderDynamicBadge({ values, prefix = '', suffix = '' }) {
return {
message: `${prefix}${values.join(', ')}${suffix}`,
color: 'brightgreen',
}
}
module.exports = {
createRoute,
queryParamSchema,
errorMessages,
renderDynamicBadge,
}

View File

@@ -0,0 +1,51 @@
'use strict'
const Joi = require('joi')
const jp = require('jsonpath')
const BaseJsonService = require('../base-json')
const { InvalidResponse } = require('../errors')
const {
createRoute,
queryParamSchema,
errorMessages,
renderDynamicBadge,
} = require('./dynamic-helpers')
module.exports = class DynamicJson extends BaseJsonService {
static get category() {
return 'dynamic'
}
static get route() {
return createRoute('json')
}
static get defaultBadgeData() {
return {
label: 'custom badge',
}
}
async handle(namedParams, queryParams) {
const {
url,
query: pathExpression,
prefix,
suffix,
} = this.constructor._validateQueryParams(queryParams, queryParamSchema)
const data = await this._requestJson({
schema: Joi.any(),
url,
errorMessages,
})
const values = jp.query(data, pathExpression)
if (!values.length) {
throw new InvalidResponse({ prettyMessage: 'no result' })
}
return renderDynamicBadge({ values, prefix, suffix })
}
}

View File

@@ -0,0 +1,156 @@
'use strict'
const Joi = require('joi')
const { expect } = require('chai')
const { colorScheme: colorsB } = require('../test-helpers')
const t = require('../create-service-tester')()
module.exports = t
t.create('Connection error')
.get(
'.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.name&label=Package Name&style=_shields_test'
)
.networkOff()
.expectJSON({
name: 'Package Name',
value: 'inaccessible',
colorB: colorsB.lightgrey,
})
t.create('No URL specified')
.get('.json?query=$.name&label=Package Name&style=_shields_test')
.expectJSON({
name: 'Package Name',
value: 'invalid query parameter: url',
colorB: colorsB.red,
})
t.create('No query specified')
.get(
'.json?url=https://github.com/badges/shields/raw/master/package.json&label=Package Name&style=_shields_test'
)
.expectJSON({
name: 'Package Name',
value: 'invalid query parameter: query',
colorB: colorsB.red,
})
t.create('Malformed url')
.get(
'.json?url=https://github.com/badges/shields/raw/master/%0package.json&query=$.name&label=Package Name&style=_shields_test'
)
.expectJSON({
name: 'Package Name',
value: 'invalid',
colorB: colorsB.lightgrey,
})
t.create('JSON from url')
.get(
'.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.name&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'shields.io',
colorB: colorsB.brightgreen,
})
t.create('JSON from uri (support uri query paramater)')
.get(
'.json?uri=https://github.com/badges/shields/raw/master/package.json&query=$.name&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'shields.io',
colorB: colorsB.brightgreen,
})
t.create('JSON from url | multiple results')
.get(
'.json?url=https://github.com/badges/shields/raw/master/package.json&query=$..keywords[0:2:1]'
)
.expectJSON({ name: 'custom badge', value: 'GitHub, badge' })
t.create('JSON from url | caching with new query params')
.get(
'.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.version'
)
.expectJSONTypes(
Joi.object().keys({
name: 'custom badge',
value: Joi.string().regex(/^\d+(\.\d+)?(\.\d+)?$/),
})
)
t.create('JSON from url | with prefix & suffix & label')
.get(
'.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.version&prefix=v&suffix= dev&label=Shields'
)
.expectJSONTypes(
Joi.object().keys({
name: 'Shields',
value: Joi.string().regex(/^v\d+(\.\d+)?(\.\d+)?\sdev$/),
})
)
t.create('JSON from url | object doesnt exist')
.get(
'.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.does_not_exist&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'no result',
colorB: colorsB.lightgrey,
})
t.create('JSON from url | invalid url')
.get(
'.json?url=https://github.com/badges/shields/raw/master/notafile.json&query=$.version&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'resource not found',
colorB: colorsB.red,
})
t.create('JSON from url | user color overrides default')
.get(
'.json?url=https://github.com/badges/shields/raw/master/package.json&query=$.name&colorB=10ADED&style=_shields_test'
)
.expectJSON({ name: 'custom badge', value: 'shields.io', colorB: '#10ADED' })
t.create('JSON from url | error color overrides default')
.get(
'.json?url=https://github.com/badges/shields/raw/master/notafile.json&query=$.version&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'resource not found',
colorB: colorsB.red,
})
// FIXME This is a regression which should be fixed in BaseService.
// t.create('JSON from url | error color overrides user specified')
// .get('.json?query=$.version&colorB=10ADED&style=_shields_test')
// .expectJSON({
// name: 'custom badge',
// value: 'invalid query parameter: url',
// colorB: colorsB.red,
// })
let headers
t.create('JSON from url | request should set Accept header')
.get('.json?url=https://json-test/api.json&query=$.name')
.intercept(nock =>
nock('https://json-test')
.get('/api.json')
.reply(200, function(uri, requestBody) {
headers = this.req.headers
return '{"name":"test"}'
})
)
.expectJSON({ name: 'custom badge', value: 'test' })
.after(() => {
expect(headers).to.have.property('accept', 'application/json')
})

View File

@@ -0,0 +1,183 @@
'use strict'
const Joi = require('joi')
const { expect } = require('chai')
const ServiceTester = require('../service-tester')
const { isSemver } = require('../test-validators')
const { colorScheme: colorsB } = require('../test-helpers')
const t = new ServiceTester({
id: 'dynamic-xml',
title: 'User Defined XML Source Data',
pathPrefix: '/badge/dynamic/xml',
})
module.exports = t
t.create('Connection error')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&label=Package Name&style=_shields_test'
)
.networkOff()
.expectJSON({
name: 'Package Name',
value: 'inaccessible',
colorB: colorsB.red,
})
t.create('No URL specified')
.get('.json?query=//name&label=Package Name&style=_shields_test')
.expectJSON({
name: 'Package Name',
value: 'no url specified',
colorB: colorsB.red,
})
t.create('No query specified')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&label=Package Name&style=_shields_test'
)
.expectJSON({
name: 'Package Name',
value: 'no query specified',
colorB: colorsB.red,
})
t.create('XML from url')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'IndieGala Helper',
colorB: colorsB.brightgreen,
})
t.create('XML from uri (support uri query paramater)')
.get(
'.json?uri=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'IndieGala Helper',
colorB: colorsB.brightgreen,
})
t.create('XML from url (attribute)')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/reviews/@num'
)
.expectJSONTypes(
Joi.object().keys({
name: 'custom badge',
value: Joi.string().regex(/^\d+$/),
})
)
t.create('XML from url | multiple results')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/compatible_applications/application/name'
)
.expectJSONTypes(
Joi.object().keys({
name: 'custom badge',
value: Joi.string().regex(
/^Firefox( for Android)?,\sFirefox( for Android)?$/
),
})
)
t.create('XML from url | caching with new query params')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/version'
)
.expectJSONTypes(
Joi.object().keys({
name: 'custom badge',
value: isSemver,
})
)
t.create('XML from url | with prefix & suffix & label')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=//version&prefix=v&suffix= dev&label=IndieGala Helper'
)
.expectJSONTypes(
Joi.object().keys({
name: 'IndieGala Helper',
value: Joi.string().regex(/^v\d+(\.\d+)?(\.\d+)?\sdev$/),
})
)
t.create('XML from url | query doesnt exist')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/does/not/exist&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'no result',
colorB: colorsB.lightgrey,
})
t.create('XML from url | query doesnt exist (attribute)')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/does/not/@exist&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'no result',
colorB: colorsB.lightgrey,
})
t.create('XML from url | invalid url')
.get(
'.json?url=https://github.com/badges/shields/raw/master/notafile.xml&query=//version&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'resource not found',
colorB: colorsB.lightgrey,
})
t.create('XML from url | user color overrides default')
.get(
'.json?url=https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/707078&query=/addon/name&colorB=10ADED&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'IndieGala Helper',
colorB: '#10ADED',
})
t.create('XML from url | error color overrides default')
.get(
'.json?url=https://github.com/badges/shields/raw/master/notafile.xml&query=//version&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'resource not found',
colorB: colorsB.lightgrey,
})
t.create('XML from url | error color overrides user specified')
.get('.json?query=//version&colorB=10ADED&style=_shields_test')
.expectJSON({
name: 'custom badge',
value: 'no url specified',
colorB: colorsB.red,
})
let headers
t.create('XML from url | request should set Accept header')
.get('.json?url=https://xml-test/api.xml&query=/name')
.intercept(nock =>
nock('https://xml-test')
.get('/api.xml')
.reply(200, function(uri, requestBody) {
headers = this.req.headers
return '<?xml version="1.0" encoding="utf-8" ?><name>dynamic xml</name>'
})
)
.expectJSON({ name: 'custom badge', value: 'dynamic xml' })
.after(() => {
expect(headers).to.have.property('accept', 'application/xml, text/xml')
})

View File

@@ -0,0 +1,122 @@
'use strict'
const ServiceTester = require('../service-tester')
const { colorScheme: colorsB } = require('../test-helpers')
const t = new ServiceTester({
id: 'dynamic-yaml',
title: 'User Defined YAML Source Data',
pathPrefix: '/badge/dynamic/yaml',
})
module.exports = t
t.create('Connection error')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&label=Package Name&style=_shields_test'
)
.networkOff()
.expectJSON({
name: 'Package Name',
value: 'inaccessible',
colorB: colorsB.red,
})
t.create('No URL specified')
.get('.json?query=$.name&label=Package Name&style=_shields_test')
.expectJSON({
name: 'Package Name',
value: 'no url specified',
colorB: colorsB.red,
})
t.create('No query specified')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&label=Package Name&style=_shields_test'
)
.expectJSON({
name: 'Package Name',
value: 'no query specified',
colorB: colorsB.red,
})
t.create('YAML from url')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'coredns',
colorB: colorsB.brightgreen,
})
t.create('YAML from uri (support uri query paramater)')
.get(
'.json?uri=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'coredns',
colorB: colorsB.brightgreen,
})
t.create('YAML from url | multiple results')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$..keywords[0:2:1]'
)
.expectJSON({ name: 'custom badge', value: 'coredns, dns' })
t.create('YAML from url | caching with new query params')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.version'
)
.expectJSON({ name: 'custom badge', value: '0.8.0' })
t.create('YAML from url | with prefix & suffix & label')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.version&prefix=v&suffix= dev&label=Shields'
)
.expectJSON({ name: 'Shields', value: 'v0.8.0 dev' })
t.create('YAML from url | object doesnt exist')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.does_not_exist&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'no result',
colorB: colorsB.lightgrey,
})
t.create('YAML from url | invalid url')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/notafile.yaml&query=$.version&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'resource not found',
colorB: colorsB.lightgrey,
})
t.create('YAML from url | user color overrides default')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/Chart.yaml&query=$.name&colorB=10ADED&style=_shields_test'
)
.expectJSON({ name: 'custom badge', value: 'coredns', colorB: '#10ADED' })
t.create('YAML from url | error color overrides default')
.get(
'.json?url=https://raw.githubusercontent.com/kubernetes/charts/568291d6e476c39ca8322c30c3f601d0383d4760/stable/coredns/notafile.yaml&query=$.version&style=_shields_test'
)
.expectJSON({
name: 'custom badge',
value: 'resource not found',
colorB: colorsB.lightgrey,
})
t.create('YAML from url | error color overrides user specified')
.get('.json?query=$.version&colorB=10ADED&style=_shields_test')
.expectJSON({
name: 'custom badge',
value: 'no url specified',
colorB: colorsB.red,
})