Skip to content

Commit

Permalink
Merge branch 'master' into test-summary
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmelnikow authored Dec 16, 2018
2 parents 0d4a6bd + b5ac5d6 commit a727346
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 58 deletions.
49 changes: 49 additions & 0 deletions services/base-yaml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict'

const BaseService = require('./base')
const emojic = require('emojic')
const { InvalidResponse } = require('./errors')
const trace = require('./trace')
const yaml = require('js-yaml')

class BaseYamlService extends BaseService {
async _requestYaml({
schema,
url,
options = {},
errorMessages = {},
encoding = 'utf8',
}) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
const mergedOptions = {
...{
headers: {
Accept:
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
},
},
...options,
}
const { buffer } = await this._request({
url,
options: mergedOptions,
errorMessages,
})
let parsed
try {
parsed = yaml.safeLoad(buffer.toString(), encoding)
} catch (err) {
logTrace(emojic.dart, 'Response YAML (unparseable)', buffer)
throw new InvalidResponse({
prettyMessage: 'unparseable yaml response',
underlyingError: err,
})
}
logTrace(emojic.dart, 'Response YAML (before validation)', parsed, {
deep: true,
})
return this.constructor._validate(parsed, schema)
}
}

module.exports = BaseYamlService
156 changes: 156 additions & 0 deletions services/base-yaml.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
'use strict'

const Joi = require('joi')
const { expect } = require('chai')
const sinon = require('sinon')
const BaseYamlService = require('./base-yaml')

const dummySchema = Joi.object({
requiredString: Joi.string().required(),
}).required()

class DummyYamlService extends BaseYamlService {
static get category() {
return 'cat'
}

static get route() {
return {
base: 'foo',
}
}

async handle() {
const { requiredString } = await this._requestYaml({
schema: dummySchema,
url: 'http://example.com/foo.yaml',
})
return { message: requiredString }
}
}

const expectedYaml = `
---
requiredString: some-string
`

const unexpectedYaml = `
---
unexpectedKey: some-string
`

const invalidYaml = `
---
foo: bar
foo: baz
`

describe('BaseYamlService', function() {
describe('Making requests', function() {
let sendAndCacheRequest
beforeEach(function() {
sendAndCacheRequest = sinon.stub().returns(
Promise.resolve({
buffer: expectedYaml,
res: { statusCode: 200 },
})
)
})

it('invokes _sendAndCacheRequest', async function() {
await DummyYamlService.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)

expect(sendAndCacheRequest).to.have.been.calledOnceWith(
'http://example.com/foo.yaml',
{
headers: {
Accept:
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
},
}
)
})

it('forwards options to _sendAndCacheRequest', async function() {
class WithOptions extends DummyYamlService {
async handle() {
const { value } = await this._requestYaml({
schema: dummySchema,
url: 'http://example.com/foo.yaml',
options: { method: 'POST', qs: { queryParam: 123 } },
})
return { message: value }
}
}

await WithOptions.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)

expect(sendAndCacheRequest).to.have.been.calledOnceWith(
'http://example.com/foo.yaml',
{
headers: {
Accept:
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
},
method: 'POST',
qs: { queryParam: 123 },
}
)
})
})

describe('Making badges', function() {
it('handles valid yaml responses', async function() {
const sendAndCacheRequest = async () => ({
buffer: expectedYaml,
res: { statusCode: 200 },
})
expect(
await DummyYamlService.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
message: 'some-string',
})
})

it('handles yaml responses which do not match the schema', async function() {
const sendAndCacheRequest = async () => ({
buffer: unexpectedYaml,
res: { statusCode: 200 },
})
expect(
await DummyYamlService.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
color: 'lightgray',
message: 'invalid response data',
})
})

it('handles unparseable yaml responses', async function() {
const sendAndCacheRequest = async () => ({
buffer: invalidYaml,
res: { statusCode: 200 },
})
expect(
await DummyYamlService.invoke(
{ sendAndCacheRequest },
{ handleInternalErrors: false }
)
).to.deep.equal({
color: 'lightgray',
message: 'unparseable yaml response',
})
})
})
})
39 changes: 6 additions & 33 deletions services/dynamic/dynamic-yaml.service.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
'use strict'

const yaml = require('js-yaml')
const jp = require('jsonpath')
const emojic = require('emojic')
const BaseService = require('../base')
const BaseYamlService = require('../base-yaml')
const { InvalidResponse } = require('../errors')
const trace = require('../trace')
const Joi = require('joi')
const jp = require('jsonpath')
const {
createRoute,
queryParamSchema,
errorMessages,
renderDynamicBadge,
} = require('./dynamic-helpers')

module.exports = class DynamicYaml extends BaseService {
module.exports = class DynamicYaml extends BaseYamlService {
static get category() {
return 'dynamic'
}
Expand All @@ -28,24 +26,6 @@ module.exports = class DynamicYaml extends BaseService {
}
}

parseYml(buffer) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
let parsed
try {
parsed = yaml.safeLoad(buffer)
} catch (err) {
logTrace(emojic.dart, 'Response YAML (unparseable)', buffer)
throw new InvalidResponse({
prettyMessage: 'unparseable yaml response',
underlyingError: err,
})
}
logTrace(emojic.dart, 'Response YAML (before validation)', parsed, {
deep: true,
})
return parsed
}

async handle(namedParams, queryParams) {
const {
url,
Expand All @@ -54,19 +34,12 @@ module.exports = class DynamicYaml extends BaseService {
suffix,
} = this.constructor._validateQueryParams(queryParams, queryParamSchema)

const { buffer } = await this._request({
const data = await this._requestYaml({
schema: Joi.any(),
url,
options: {
headers: {
Accept:
'text/x-yaml, text/yaml, application/x-yaml, application/yaml, text/plain',
},
},
errorMessages,
})

const data = this.parseYml(buffer)

const values = jp.query(data, pathExpression)

if (!values.length) {
Expand Down
33 changes: 11 additions & 22 deletions services/f-droid/f-droid.service.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
'use strict'

const Joi = require('joi')
const yaml = require('js-yaml')
const BaseService = require('../base')
const BaseYamlService = require('../base-yaml')
const { addv: versionText } = require('../../lib/text-formatters')
const { version: versionColor } = require('../../lib/color-formatters')
const { InvalidResponse } = require('../errors')

module.exports = class FDroid extends BaseService {
const schema = Joi.object({
CurrentVersion: Joi.alternatives()
.try(Joi.number(), Joi.string())
.required(),
}).required()

module.exports = class FDroid extends BaseYamlService {
static render({ version }) {
return {
message: versionText(version),
Expand Down Expand Up @@ -45,28 +50,12 @@ module.exports = class FDroid extends BaseService {
}

async fetchYaml(url, options) {
const { buffer } = await this._request({
const yaml = await this._requestYaml({
schema,
url: `${url}.yml`,
...options,
})

// we assume the yaml layout as provided here:
// https://gitlab.com/fdroid/fdroiddata/raw/master/metadata/org.dystopia.email.yml
try {
const { CurrentVersion: version } = yaml.safeLoad(
buffer.toString(),
'utf8'
)
if (!version) {
throw new Error('could not find version on website')
}
return { version }
} catch (error) {
throw new InvalidResponse({
prettyMessage: 'invalid response',
underlyingError: error,
})
}
return { version: yaml['CurrentVersion'] }
}

async fetchText(url, options) {
Expand Down
6 changes: 3 additions & 3 deletions services/f-droid/f-droid.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ t.create('Package is found yml matadata format with missing "CurrentVersion"')
.get(`${path}.yml`)
.reply(200, 'Categories: System')
)
.expectJSON({ name: 'f-droid', value: 'invalid response' })
.expectJSON({ name: 'f-droid', value: 'invalid response data' })

t.create('Package is found with bad yml matadata format')
.get('/v/axp.tool.apkextractor.json?metadata_format=yml')
Expand All @@ -164,7 +164,7 @@ t.create('Package is found with bad yml matadata format')
.get(`${path}.yml`)
.reply(200, '.CurrentVersion: 1.4')
)
.expectJSON({ name: 'f-droid', value: 'invalid response' })
.expectJSON({ name: 'f-droid', value: 'invalid response data' })

t.create('Package is not found')
.get('/v/axp.tool.apkextractor.json')
Expand All @@ -187,7 +187,7 @@ t.create('The api changed')
.get(`${path}.yml`)
.reply(200, '')
)
.expectJSON({ name: 'f-droid', value: 'invalid response' })
.expectJSON({ name: 'f-droid', value: 'invalid response data' })

t.create('Package is not found due invalid metadata format')
.get('/v/axp.tool.apkextractor.json?metadata_format=xml')
Expand Down

0 comments on commit a727346

Please sign in to comment.