Three main goals:
1. In the front end:
a. Show form fields and automatically assemble badge URLs (#701)
c. Group together examples for the same service
b. Show deprecated services
2. Make it easy to changing the schema of `examples`, thanks to 100% validation. One challenge with frameworks is that when there are typos things fail silently which is pretty unfriendly to developers. The validation should really help with that. (This caught one bug in AUR, though I fixed it in #2405 which landed first.)
3. Produce a service definition export for external tool builders. (#776)
4. Build toward harmony between the front-end data structure and the `examples` key in the service classes. I aliased `staticPreview` to `staticExample` which starts this process.
The old format:
- Lacked a consistent machine-readable representation of the fields.
- Flattened multiple examples for the same service were flattened.
- Excluded deprecated services.
The new format improves a few things, too:
- It cleans up the naming. Since this file evolved over time, the names were a bit muddled (i.e. what was an example vs a preview).
- It duplicated information (like `.svg`). (I can imagine dropping the `.svg` from our badge URLs someday, which would make the URLs easier to read and maintain.)
- For a human reading the YAML file, providing the static example as a deconstructed object is more readable.
Here are a couple snippets:
```yml
- category: build
name: AppVeyorCi
isDeprecated: false
route:
format: '([^/]+/[^/]+)(?:/(.+))?'
queryParams: []
examples:
- title: AppVeyor
example: {path: /appveyor/ci/gruntjs/grunt, queryParams: {}}
preview: {label: build, message: passing, color: brightgreen}
keywords: []
- title: AppVeyor branch
example: {path: /appveyor/ci/gruntjs/grunt/master, queryParams: {}}
preview: {label: build, message: passing, color: brightgreen}
keywords: []
- category: downloads
name: AmoDownloads
isDeprecated: false
examples:
- title: Mozilla Add-on
example: {path: /amo/d/dustman, queryParams: {}}
preview: {path: /amo/d/dustman, queryParams: {}}
keywords: [amo, firefox]
```
682 lines
19 KiB
JavaScript
682 lines
19 KiB
JavaScript
'use strict'
|
|
|
|
const Joi = require('joi')
|
|
const { expect } = require('chai')
|
|
const { test, given, forCases } = require('sazerac')
|
|
const sinon = require('sinon')
|
|
const trace = require('./trace')
|
|
|
|
const {
|
|
NotFound,
|
|
Inaccessible,
|
|
InvalidResponse,
|
|
InvalidParameter,
|
|
Deprecated,
|
|
} = require('./errors')
|
|
const BaseService = require('./base')
|
|
|
|
require('../lib/register-chai-plugins.spec')
|
|
|
|
class DummyService extends BaseService {
|
|
static render({ namedParamA, queryParamA }) {
|
|
return {
|
|
message: `Hello namedParamA: ${namedParamA} with queryParamA: ${queryParamA}`,
|
|
}
|
|
}
|
|
|
|
async handle({ namedParamA }, { queryParamA }) {
|
|
return this.constructor.render({ namedParamA, queryParamA })
|
|
}
|
|
|
|
static get category() {
|
|
return 'cat'
|
|
}
|
|
|
|
static get examples() {
|
|
return [
|
|
{ previewUrl: 'World' },
|
|
{ previewUrl: 'World', query: { queryParamA: '!!!' } },
|
|
{
|
|
urlPattern: ':world',
|
|
exampleUrl: 'World',
|
|
staticExample: this.render({ namedParamA: 'foo', queryParamA: 'bar' }),
|
|
keywords: ['hello'],
|
|
},
|
|
{
|
|
pattern: ':world',
|
|
exampleUrl: 'World',
|
|
staticExample: this.render({ namedParamA: 'foo', queryParamA: 'bar' }),
|
|
keywords: ['hello'],
|
|
},
|
|
{
|
|
pattern: ':world',
|
|
namedParams: { world: 'World' },
|
|
staticExample: this.render({ namedParamA: 'foo', queryParamA: 'bar' }),
|
|
keywords: ['hello'],
|
|
},
|
|
{
|
|
pattern: ':world',
|
|
namedParams: { world: 'World' },
|
|
query: { queryParamA: '!!!' },
|
|
staticExample: this.render({ namedParamA: 'foo', queryParamA: 'bar' }),
|
|
keywords: ['hello'],
|
|
},
|
|
]
|
|
}
|
|
static get route() {
|
|
return {
|
|
base: 'foo',
|
|
pattern: ':namedParamA',
|
|
queryParams: ['queryParamA'],
|
|
}
|
|
}
|
|
}
|
|
|
|
describe('BaseService', function() {
|
|
const defaultConfig = { handleInternalErrors: false }
|
|
|
|
describe('URL pattern matching', function() {
|
|
context('A `pattern` with a named param is declared', function() {
|
|
const regexExec = str => DummyService._regex.exec(str)
|
|
const getNamedParamA = str => {
|
|
const [, namedParamA] = regexExec(str)
|
|
return namedParamA
|
|
}
|
|
const namedParams = str => {
|
|
const match = regexExec(str)
|
|
return DummyService._namedParamsForMatch(match)
|
|
}
|
|
|
|
test(regexExec, () => {
|
|
forCases([
|
|
given('/foo/bar.bar.bar.zip'),
|
|
given('/foo/bar/bar.svg'),
|
|
// This is a valid example with the wrong extension separator, to
|
|
// test that we only accept a `.`.
|
|
given('/foo/bar.bar.bar_svg'),
|
|
]).expect(null)
|
|
})
|
|
|
|
test(getNamedParamA, () => {
|
|
forCases([
|
|
given('/foo/bar.bar.bar.svg'),
|
|
given('/foo/bar.bar.bar.png'),
|
|
given('/foo/bar.bar.bar.gif'),
|
|
given('/foo/bar.bar.bar.jpg'),
|
|
given('/foo/bar.bar.bar.json'),
|
|
]).expect('bar.bar.bar')
|
|
})
|
|
|
|
test(namedParams, () => {
|
|
forCases([
|
|
given('/foo/bar.bar.bar.svg'),
|
|
given('/foo/bar.bar.bar.png'),
|
|
given('/foo/bar.bar.bar.gif'),
|
|
given('/foo/bar.bar.bar.jpg'),
|
|
given('/foo/bar.bar.bar.json'),
|
|
]).expect({ namedParamA: 'bar.bar.bar' })
|
|
})
|
|
})
|
|
|
|
context('A `format` with a named param is declared', function() {
|
|
class ServiceWithFormat extends BaseService {
|
|
static get route() {
|
|
return {
|
|
base: 'foo',
|
|
format: '([^/]+)',
|
|
capture: ['namedParamA'],
|
|
}
|
|
}
|
|
}
|
|
|
|
const regexExec = str => ServiceWithFormat._regex.exec(str)
|
|
const getNamedParamA = str => {
|
|
const [, namedParamA] = regexExec(str)
|
|
return namedParamA
|
|
}
|
|
const namedParams = str => {
|
|
const match = regexExec(str)
|
|
return ServiceWithFormat._namedParamsForMatch(match)
|
|
}
|
|
|
|
test(regexExec, () => {
|
|
forCases([
|
|
given('/foo/bar.bar.bar.zip'),
|
|
given('/foo/bar/bar.svg'),
|
|
// This is a valid example with the wrong extension separator, to
|
|
// test that we only accept a `.`.
|
|
given('/foo/bar.bar.bar_svg'),
|
|
]).expect(null)
|
|
})
|
|
|
|
test(getNamedParamA, () => {
|
|
forCases([
|
|
given('/foo/bar.bar.bar.svg'),
|
|
given('/foo/bar.bar.bar.png'),
|
|
given('/foo/bar.bar.bar.gif'),
|
|
given('/foo/bar.bar.bar.jpg'),
|
|
given('/foo/bar.bar.bar.json'),
|
|
]).expect('bar.bar.bar')
|
|
})
|
|
|
|
test(namedParams, () => {
|
|
forCases([
|
|
given('/foo/bar.bar.bar.svg'),
|
|
given('/foo/bar.bar.bar.png'),
|
|
given('/foo/bar.bar.bar.gif'),
|
|
given('/foo/bar.bar.bar.jpg'),
|
|
given('/foo/bar.bar.bar.json'),
|
|
]).expect({ namedParamA: 'bar.bar.bar' })
|
|
})
|
|
})
|
|
|
|
context('No named params are declared', function() {
|
|
class ServiceWithZeroNamedParams extends BaseService {
|
|
static get route() {
|
|
return {
|
|
base: 'foo',
|
|
format: '(?:[^/]+)',
|
|
}
|
|
}
|
|
}
|
|
|
|
const namedParams = str => {
|
|
const match = ServiceWithZeroNamedParams._regex.exec(str)
|
|
return ServiceWithZeroNamedParams._namedParamsForMatch(match)
|
|
}
|
|
|
|
test(namedParams, () => {
|
|
forCases([
|
|
given('/foo/bar.bar.bar.svg'),
|
|
given('/foo/bar.bar.bar.png'),
|
|
given('/foo/bar.bar.bar.gif'),
|
|
given('/foo/bar.bar.bar.jpg'),
|
|
given('/foo/bar.bar.bar.json'),
|
|
]).expect({})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('Invokes the handler as expected', async function() {
|
|
expect(
|
|
await DummyService.invoke(
|
|
{},
|
|
defaultConfig,
|
|
{ namedParamA: 'bar.bar.bar' },
|
|
{ queryParamA: '!' }
|
|
)
|
|
).to.deep.equal({
|
|
message: 'Hello namedParamA: bar.bar.bar with queryParamA: !',
|
|
})
|
|
})
|
|
|
|
describe('Logging', function() {
|
|
let sandbox
|
|
beforeEach(function() {
|
|
sandbox = sinon.createSandbox()
|
|
})
|
|
afterEach(function() {
|
|
sandbox.restore()
|
|
})
|
|
beforeEach(function() {
|
|
sandbox.stub(trace, 'logTrace')
|
|
})
|
|
it('Invokes the logger as expected', async function() {
|
|
await DummyService.invoke(
|
|
{},
|
|
defaultConfig,
|
|
{ namedParamA: 'bar.bar.bar' },
|
|
{ queryParamA: '!' }
|
|
)
|
|
expect(trace.logTrace).to.be.calledWithMatch(
|
|
'inbound',
|
|
sinon.match.string,
|
|
'Service class',
|
|
'DummyService'
|
|
)
|
|
expect(trace.logTrace).to.be.calledWith(
|
|
'inbound',
|
|
sinon.match.string,
|
|
'Named params',
|
|
{ namedParamA: 'bar.bar.bar' }
|
|
)
|
|
expect(trace.logTrace).to.be.calledWith(
|
|
'inbound',
|
|
sinon.match.string,
|
|
'Query params',
|
|
{ queryParamA: '!' }
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('Error handling', function() {
|
|
it('Handles internal errors', async function() {
|
|
class ThrowingService extends DummyService {
|
|
async handle() {
|
|
throw Error("I've made a huge mistake")
|
|
}
|
|
}
|
|
expect(
|
|
await ThrowingService.invoke(
|
|
{},
|
|
{ handleInternalErrors: true },
|
|
{ namedParamA: 'bar.bar.bar' }
|
|
)
|
|
).to.deep.equal({
|
|
color: 'lightgray',
|
|
label: 'shields',
|
|
message: 'internal error',
|
|
})
|
|
})
|
|
|
|
describe('Handles known subtypes of ShieldsInternalError', function() {
|
|
it('handles NotFound errors', async function() {
|
|
class ThrowingService extends DummyService {
|
|
async handle() {
|
|
throw new NotFound()
|
|
}
|
|
}
|
|
expect(
|
|
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
|
|
).to.deep.equal({
|
|
color: 'red',
|
|
message: 'not found',
|
|
})
|
|
})
|
|
|
|
it('handles Inaccessible errors', async function() {
|
|
class ThrowingService extends DummyService {
|
|
async handle() {
|
|
throw new Inaccessible()
|
|
}
|
|
}
|
|
expect(
|
|
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
|
|
).to.deep.equal({
|
|
color: 'lightgray',
|
|
message: 'inaccessible',
|
|
})
|
|
})
|
|
|
|
it('handles InvalidResponse errors', async function() {
|
|
class ThrowingService extends DummyService {
|
|
async handle() {
|
|
throw new InvalidResponse()
|
|
}
|
|
}
|
|
expect(
|
|
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
|
|
).to.deep.equal({
|
|
color: 'lightgray',
|
|
message: 'invalid',
|
|
})
|
|
})
|
|
|
|
it('handles Deprecated', async function() {
|
|
class ThrowingService extends DummyService {
|
|
async handle() {
|
|
throw new Deprecated()
|
|
}
|
|
}
|
|
expect(
|
|
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
|
|
).to.deep.equal({
|
|
color: 'lightgray',
|
|
message: 'no longer available',
|
|
})
|
|
})
|
|
|
|
it('handles InvalidParameter errors', async function() {
|
|
class ThrowingService extends DummyService {
|
|
async handle() {
|
|
throw new InvalidParameter()
|
|
}
|
|
}
|
|
expect(
|
|
await ThrowingService.invoke({}, {}, { namedParamA: 'bar.bar.bar' })
|
|
).to.deep.equal({
|
|
color: 'red',
|
|
message: 'invalid parameter',
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('_makeBadgeData', function() {
|
|
describe('Overrides', function() {
|
|
it('overrides the label', function() {
|
|
const badgeData = DummyService._makeBadgeData(
|
|
{ label: 'purr count' },
|
|
{ label: 'purrs' }
|
|
)
|
|
expect(badgeData.text).to.deep.equal(['purr count', 'n/a'])
|
|
})
|
|
|
|
it('overrides the color', function() {
|
|
const badgeData = DummyService._makeBadgeData(
|
|
{ colorB: '10ADED' },
|
|
{ color: 'red' }
|
|
)
|
|
expect(badgeData.colorB).to.equal('#10ADED')
|
|
})
|
|
})
|
|
|
|
describe('Service data', function() {
|
|
it('applies the service message', function() {
|
|
const badgeData = DummyService._makeBadgeData({}, { message: '10k' })
|
|
expect(badgeData.text).to.deep.equal(['cat', '10k'])
|
|
})
|
|
|
|
it('preserves an empty label', function() {
|
|
const badgeData = DummyService._makeBadgeData(
|
|
{},
|
|
{ label: '', message: '10k' }
|
|
)
|
|
expect(badgeData.text).to.deep.equal(['', '10k'])
|
|
})
|
|
|
|
it('applies a numeric service message', function() {
|
|
// While a number of badges use this, in the long run we may want
|
|
// `render()` to always return a string.
|
|
const badgeData = DummyService._makeBadgeData({}, { message: 10 })
|
|
expect(badgeData.text).to.deep.equal(['cat', 10])
|
|
})
|
|
|
|
it('applies the service color', function() {
|
|
const badgeData = DummyService._makeBadgeData({}, { color: 'red' })
|
|
expect(badgeData.colorscheme).to.equal('red')
|
|
})
|
|
})
|
|
|
|
describe('Defaults', function() {
|
|
it('uses the default label', function() {
|
|
const badgeData = DummyService._makeBadgeData({}, {})
|
|
expect(badgeData.text).to.deep.equal(['cat', 'n/a'])
|
|
})
|
|
|
|
it('uses the default color', function() {
|
|
const badgeData = DummyService._makeBadgeData({}, {})
|
|
expect(badgeData.colorscheme).to.equal('lightgrey')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('ScoutCamp integration', function() {
|
|
const expectedRouteRegex = /^\/foo\/([^/]+?)\.(svg|png|gif|jpg|json)$/
|
|
|
|
let mockCamp
|
|
let mockHandleRequest
|
|
|
|
beforeEach(function() {
|
|
mockCamp = {
|
|
route: sinon.spy(),
|
|
}
|
|
mockHandleRequest = sinon.spy()
|
|
DummyService.register(
|
|
{ camp: mockCamp, handleRequest: mockHandleRequest },
|
|
defaultConfig
|
|
)
|
|
})
|
|
|
|
it('registers the service', function() {
|
|
expect(mockCamp.route).to.have.been.calledOnce
|
|
expect(mockCamp.route).to.have.been.calledWith(expectedRouteRegex)
|
|
})
|
|
|
|
it('handles the request', async function() {
|
|
expect(mockHandleRequest).to.have.been.calledOnce
|
|
const { handler: requestHandler } = mockHandleRequest.getCall(0).args[1]
|
|
|
|
const mockSendBadge = sinon.spy()
|
|
const mockRequest = {
|
|
asPromise: sinon.spy(),
|
|
}
|
|
const queryParams = { queryParamA: '?' }
|
|
const match = '/foo/bar.svg'.match(expectedRouteRegex)
|
|
await requestHandler(queryParams, match, mockSendBadge, mockRequest)
|
|
|
|
const expectedFormat = 'svg'
|
|
expect(mockSendBadge).to.have.been.calledOnce
|
|
expect(mockSendBadge).to.have.been.calledWith(expectedFormat, {
|
|
text: ['cat', 'Hello namedParamA: bar with queryParamA: ?'],
|
|
colorscheme: 'lightgrey',
|
|
template: undefined,
|
|
logo: undefined,
|
|
logoWidth: NaN,
|
|
links: [],
|
|
colorA: undefined,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('_makeStaticExampleUrl', function() {
|
|
test(
|
|
serviceData => DummyService._makeStaticExampleUrl(serviceData),
|
|
() => {
|
|
given({
|
|
message: 'hello',
|
|
color: 'dcdc00',
|
|
}).expect('/badge/cat-hello-%23dcdc00.svg')
|
|
given({
|
|
message: 'hello',
|
|
color: 'red',
|
|
}).expect('/badge/cat-hello-red.svg')
|
|
given({
|
|
message: 'hello',
|
|
}).expect('/badge/cat-hello-lightgrey.svg')
|
|
}
|
|
)
|
|
})
|
|
|
|
describe('prepareExamples', function() {
|
|
it('returns the expected result', function() {
|
|
const [
|
|
first,
|
|
second,
|
|
third,
|
|
fourth,
|
|
fifth,
|
|
sixth,
|
|
] = DummyService.prepareExamples()
|
|
expect(first).to.deep.equal({
|
|
title: 'DummyService',
|
|
exampleUrl: undefined,
|
|
previewUrl: '/foo/World.svg',
|
|
urlPattern: undefined,
|
|
documentation: undefined,
|
|
keywords: undefined,
|
|
})
|
|
expect(second).to.deep.equal({
|
|
title: 'DummyService',
|
|
exampleUrl: undefined,
|
|
previewUrl: '/foo/World.svg?queryParamA=%21%21%21',
|
|
urlPattern: undefined,
|
|
documentation: undefined,
|
|
keywords: undefined,
|
|
})
|
|
const preparedStaticExample = {
|
|
title: 'DummyService',
|
|
exampleUrl: '/foo/World.svg',
|
|
previewUrl:
|
|
'/badge/cat-Hello%20namedParamA%3A%20foo%20with%20queryParamA%3A%20bar-lightgrey.svg',
|
|
urlPattern: '/foo/:world.svg',
|
|
documentation: undefined,
|
|
keywords: ['hello'],
|
|
}
|
|
expect(third).to.deep.equal(preparedStaticExample)
|
|
expect(fourth).to.deep.equal(preparedStaticExample)
|
|
expect(fifth).to.deep.equal(preparedStaticExample)
|
|
expect(sixth).to.deep.equal({
|
|
title: 'DummyService',
|
|
exampleUrl: '/foo/World.svg?queryParamA=%21%21%21',
|
|
previewUrl:
|
|
'/badge/cat-Hello%20namedParamA%3A%20foo%20with%20queryParamA%3A%20bar-lightgrey.svg',
|
|
urlPattern: '/foo/:world.svg?queryParamA=%21%21%21',
|
|
documentation: undefined,
|
|
keywords: ['hello'],
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('getDefinition', function() {
|
|
it('returns the expected result', function() {
|
|
const {
|
|
examples: [first, second, third, fourth, fifth, sixth],
|
|
} = DummyService.getDefinition()
|
|
expect(first).to.deep.equal({
|
|
title: 'DummyService',
|
|
example: {
|
|
path: '/foo/World',
|
|
queryParams: {},
|
|
},
|
|
preview: {
|
|
path: '/foo/World',
|
|
queryParams: {},
|
|
},
|
|
keywords: [],
|
|
documentation: undefined,
|
|
})
|
|
expect(second).to.deep.equal({
|
|
title: 'DummyService',
|
|
example: {
|
|
path: '/foo/World',
|
|
queryParams: { queryParamA: '!!!' },
|
|
},
|
|
preview: {
|
|
path: '/foo/World',
|
|
queryParams: { queryParamA: '!!!' },
|
|
},
|
|
keywords: [],
|
|
documentation: undefined,
|
|
})
|
|
const expectedDefinition = {
|
|
title: 'DummyService',
|
|
example: {
|
|
path: '/foo/World',
|
|
queryParams: {},
|
|
},
|
|
preview: {
|
|
label: 'cat',
|
|
message: 'Hello namedParamA: foo with queryParamA: bar',
|
|
color: 'lightgrey',
|
|
},
|
|
keywords: ['hello'],
|
|
documentation: undefined,
|
|
}
|
|
expect(third).to.deep.equal(expectedDefinition)
|
|
expect(fourth).to.deep.equal(expectedDefinition)
|
|
expect(fifth).to.deep.equal({
|
|
title: 'DummyService',
|
|
example: {
|
|
pattern: '/foo/:world',
|
|
namedParams: { world: 'World' },
|
|
queryParams: {},
|
|
},
|
|
preview: {
|
|
label: 'cat',
|
|
message: 'Hello namedParamA: foo with queryParamA: bar',
|
|
color: 'lightgrey',
|
|
},
|
|
keywords: ['hello'],
|
|
documentation: undefined,
|
|
})
|
|
expect(sixth).to.deep.equal({
|
|
title: 'DummyService',
|
|
example: {
|
|
pattern: '/foo/:world',
|
|
namedParams: { world: 'World' },
|
|
queryParams: { queryParamA: '!!!' },
|
|
},
|
|
preview: {
|
|
color: 'lightgrey',
|
|
label: 'cat',
|
|
message: 'Hello namedParamA: foo with queryParamA: bar',
|
|
},
|
|
keywords: ['hello'],
|
|
documentation: undefined,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('validate', function() {
|
|
const dummySchema = Joi.object({
|
|
requiredString: Joi.string().required(),
|
|
}).required()
|
|
|
|
it('throws error for invalid responses', async function() {
|
|
try {
|
|
DummyService._validate(
|
|
{ requiredString: ['this', "shouldn't", 'work'] },
|
|
dummySchema
|
|
)
|
|
expect.fail('Expected to throw')
|
|
} catch (e) {
|
|
expect(e).to.be.an.instanceof(InvalidResponse)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('request', function() {
|
|
let sandbox
|
|
beforeEach(function() {
|
|
sandbox = sinon.createSandbox()
|
|
})
|
|
afterEach(function() {
|
|
sandbox.restore()
|
|
})
|
|
beforeEach(function() {
|
|
sandbox.stub(trace, 'logTrace')
|
|
})
|
|
|
|
it('logs appropriate information', async function() {
|
|
const sendAndCacheRequest = async () => ({
|
|
buffer: '',
|
|
res: { statusCode: 200 },
|
|
})
|
|
const serviceInstance = new DummyService(
|
|
{ sendAndCacheRequest },
|
|
defaultConfig
|
|
)
|
|
|
|
const url = 'some-url'
|
|
const options = { headers: { Cookie: 'some-cookie' } }
|
|
await serviceInstance._request({ url, options })
|
|
|
|
expect(trace.logTrace).to.be.calledWithMatch(
|
|
'fetch',
|
|
sinon.match.string,
|
|
'Request',
|
|
url,
|
|
'\n',
|
|
options
|
|
)
|
|
expect(trace.logTrace).to.be.calledWithMatch(
|
|
'fetch',
|
|
sinon.match.string,
|
|
'Response status code',
|
|
200
|
|
)
|
|
})
|
|
|
|
it('handles errors', async function() {
|
|
const sendAndCacheRequest = async () => ({
|
|
buffer: '',
|
|
res: { statusCode: 404 },
|
|
})
|
|
const serviceInstance = new DummyService(
|
|
{ sendAndCacheRequest },
|
|
defaultConfig
|
|
)
|
|
|
|
try {
|
|
await serviceInstance._request({})
|
|
expect.fail('Expected to throw')
|
|
} catch (e) {
|
|
expect(e).to.be.an.instanceof(NotFound)
|
|
expect(e.message).to.equal('Not Found')
|
|
expect(e.prettyMessage).to.equal('not found')
|
|
}
|
|
})
|
|
})
|
|
})
|