diff --git a/lib/error-helper.js b/lib/error-helper.js index 0fed84ae32..71aee42843 100644 --- a/lib/error-helper.js +++ b/lib/error-helper.js @@ -1,6 +1,10 @@ 'use strict' -const { NotFound, InvalidResponse } = require('../services/errors') +const { + NotFound, + InvalidResponse, + Inaccessible, +} = require('../services/errors') const checkErrorResponse = function( badgeData, @@ -27,13 +31,15 @@ const checkErrorResponse = function( checkErrorResponse.asPromise = function({ notFoundMessage } = {}) { return async function({ buffer, res }) { + const underlyingError = Error( + `Got status code ${res.statusCode} (expected 200)` + ) if (res.statusCode === 404) { throw new NotFound({ prettyMessage: notFoundMessage }) + } else if (res.statusCode >= 500) { + throw new Inaccessible({ underlyingError }) } else if (res.statusCode !== 200) { - const underlying = Error( - `Got status code ${res.statusCode} (expected 200)` - ) - throw new InvalidResponse({ underlyingError: underlying }) + throw new InvalidResponse({ underlyingError }) } return { buffer, res } } diff --git a/lib/error-helper.spec.js b/lib/error-helper.spec.js index 35729f06df..92f33cef83 100644 --- a/lib/error-helper.spec.js +++ b/lib/error-helper.spec.js @@ -3,7 +3,11 @@ const chai = require('chai') const { assert, expect } = chai const { checkErrorResponse } = require('./error-helper') -const { NotFound, InvalidResponse } = require('../services/errors') +const { + NotFound, + InvalidResponse, + Inaccessible, +} = require('../services/errors') chai.use(require('chai-as-promised')) @@ -86,8 +90,8 @@ describe('async error handler', function() { }) }) - context('when status is 500', function() { - const res = { statusCode: 500 } + context('when status is 4xx', function() { + const res = { statusCode: 499 } it('throws InvalidResponse', async function() { try { @@ -96,10 +100,27 @@ describe('async error handler', function() { } catch (e) { expect(e).to.be.an.instanceof(InvalidResponse) expect(e.message).to.equal( - 'Invalid Response: Got status code 500 (expected 200)' + 'Invalid Response: Got status code 499 (expected 200)' ) expect(e.prettyMessage).to.equal('invalid') } }) }) + + context('when status is 5xx', function() { + const res = { statusCode: 503 } + + it('throws Inaccessible', async function() { + try { + await checkErrorResponse.asPromise()({ res }) + expect.fail('Expected to throw') + } catch (e) { + expect(e).to.be.an.instanceof(Inaccessible) + expect(e.message).to.equal( + 'Inaccessible: Got status code 503 (expected 200)' + ) + expect(e.prettyMessage).to.equal('inaccessible') + } + }) + }) }) diff --git a/services/base.js b/services/base.js index d36819815e..ce011f4f6f 100644 --- a/services/base.js +++ b/services/base.js @@ -3,7 +3,12 @@ // See available emoji at http://emoji.muan.co/ const emojic = require('emojic') const chalk = require('chalk') -const { NotFound, InvalidResponse, Inaccessible } = require('./errors') +const { + NotFound, + InvalidResponse, + Inaccessible, + InvalidParameter, +} = require('./errors') const queryString = require('query-string') const { makeLogo, @@ -162,7 +167,7 @@ class BaseService { try { return await this.handle(namedParams, queryParams) } catch (error) { - if (error instanceof NotFound) { + if (error instanceof NotFound || error instanceof InvalidParameter) { logTrace('outbound', emojic.noGoodWoman, 'Handled error', error) return { message: error.prettyMessage, diff --git a/services/base.spec.js b/services/base.spec.js index 2881bc2b67..0b7e8ef6dc 100644 --- a/services/base.spec.js +++ b/services/base.spec.js @@ -4,6 +4,12 @@ const { expect } = require('chai') const { test, given, forCases } = require('sazerac') const sinon = require('sinon') +const { + NotFound, + Inaccessible, + InvalidResponse, + InvalidParameter, +} = require('./errors') const BaseService = require('./base') require('../lib/register-chai-plugins.spec') @@ -125,7 +131,7 @@ describe('BaseService', () => { }) }) - describe('Error handling', function() { + describe.only('Error handling', function() { it('Handles internal errors', async function() { const serviceInstance = new DummyService( {}, @@ -134,15 +140,79 @@ describe('BaseService', () => { serviceInstance.handle = () => { throw Error("I've made a huge mistake") } - const serviceData = await serviceInstance.invokeHandler({ - namedParamA: 'bar.bar.bar', - }) - expect(serviceData).to.deep.equal({ + expect( + await serviceInstance.invokeHandler({ + namedParamA: 'bar.bar.bar', + }) + ).to.deep.equal({ color: 'lightgray', label: 'shields', message: 'internal error', }) }) + + describe('Handles known subtypes of ShieldsInternalError', function() { + let serviceInstance + beforeEach(function() { + serviceInstance = new DummyService({}, {}) + }) + + it('handles NotFound errors', async function() { + serviceInstance.handle = () => { + throw new NotFound() + } + expect( + await serviceInstance.invokeHandler({ + namedParamA: 'bar.bar.bar', + }) + ).to.deep.equal({ + color: 'red', + message: 'not found', + }) + }) + + it('handles Inaccessible errors', async function() { + serviceInstance.handle = () => { + throw new Inaccessible() + } + expect( + await serviceInstance.invokeHandler({ + namedParamA: 'bar.bar.bar', + }) + ).to.deep.equal({ + color: 'lightgray', + message: 'inaccessible', + }) + }) + + it('handles InvalidResponse errors', async function() { + serviceInstance.handle = () => { + throw new InvalidResponse() + } + expect( + await serviceInstance.invokeHandler({ + namedParamA: 'bar.bar.bar', + }) + ).to.deep.equal({ + color: 'lightgray', + message: 'invalid', + }) + }) + + it('handles InvalidParameter errors', async function() { + serviceInstance.handle = () => { + throw new InvalidParameter() + } + expect( + await serviceInstance.invokeHandler({ + namedParamA: 'bar.bar.bar', + }) + ).to.deep.equal({ + color: 'red', + message: 'invalid parameter', + }) + }) + }) }) describe('_makeBadgeData', function() { diff --git a/services/errors.js b/services/errors.js index 68131efc35..ad8c070774 100644 --- a/services/errors.js +++ b/services/errors.js @@ -69,8 +69,23 @@ class Inaccessible extends ShieldsRuntimeError { } } +class InvalidParameter extends ShieldsRuntimeError { + get name() { + return 'InvalidParameter' + } + get defaultPrettyMessage() { + return 'invalid parameter' + } + + constructor(props) { + const message = 'Invalid Parameter' + super(props, message) + } +} + module.exports = { NotFound, InvalidResponse, Inaccessible, + InvalidParameter, }