diff --git a/src/Service/Request.service.js b/src/Service/Request.service.js index e7bde721..9e30da64 100644 --- a/src/Service/Request.service.js +++ b/src/Service/Request.service.js @@ -12,10 +12,28 @@ import DependencyAwareClass from '../DependencyInjection/DependencyAware.class'; import ResponseModel from '../Model/Response.model'; export const REQUEST_TYPES = { + DELETE: 'DELETE', GET: 'GET', + HEAD: 'HEAD', + OPTIONS: 'OPTIONS', + PATCH: 'PATCH', POST: 'POST', + PUT: 'PUT', }; +export const HTTP_METHODS_WITHOUT_PAYLOADS = [ + REQUEST_TYPES.DELETE, + REQUEST_TYPES.GET, + REQUEST_TYPES.HEAD, + REQUEST_TYPES.OPTIONS, +]; + +export const HTTP_METHODS_WITH_PAYLOADS = [ + REQUEST_TYPES.PATCH, + REQUEST_TYPES.POST, + REQUEST_TYPES.PUT, +]; + // Define action specific error types export const ERROR_TYPES = { VALIDATION_ERROR: new ResponseModel({}, 400, 'required fields are missing'), @@ -130,11 +148,11 @@ export default class RequestService extends DependencyAwareClass { getAll(requestType = null) { const event = this.getContainer().getEvent(); - if (event.httpMethod === 'GET' || requestType === REQUEST_TYPES.GET) { + if (HTTP_METHODS_WITHOUT_PAYLOADS.includes(event.httpMethod) || HTTP_METHODS_WITHOUT_PAYLOADS.includes(requestType)) { return typeof event.queryStringParameters !== 'undefined' ? event.queryStringParameters : {}; } - if (event.httpMethod === 'POST' || requestType === REQUEST_TYPES.POST) { + if (HTTP_METHODS_WITH_PAYLOADS.includes(event.httpMethod) || HTTP_METHODS_WITH_PAYLOADS.includes(requestType)) { const contentType = this.getHeader('Content-Type'); let queryParameters = {}; @@ -152,11 +170,7 @@ export default class RequestService extends DependencyAwareClass { if (contentType.includes('text/xml')) { XML2JS.parseString(event.body, (error, result) => { - if (error) { - queryParameters = {}; - } else { - queryParameters = result; - } + queryParameters = error ? {} : result; }); } diff --git a/tests/unit/Service/Request.service.test.js b/tests/unit/Service/Request.service.test.js index c533447a..557b4b5b 100644 --- a/tests/unit/Service/Request.service.test.js +++ b/tests/unit/Service/Request.service.test.js @@ -5,224 +5,216 @@ import sinon from 'sinon'; import CONFIGURATION from '../../../src/Config/Dependencies'; import DependencyInjection from '../../../src/DependencyInjection/DependencyInjection.class'; -import RequestService, { REQUEST_TYPES } from '../../../src/Service/Request.service'; +import RequestService, { HTTP_METHODS_WITHOUT_PAYLOADS, HTTP_METHODS_WITH_PAYLOADS } from '../../../src/Service/Request.service'; const getContext = require('../../mocks/aws/context.json'); -const getEvent = require('../../mocks/aws/event.json'); +const baseEvent = require('../../mocks/aws/event.json'); + +const getEvent = (overrides = {}) => JSON.parse(JSON.stringify(({ + ...baseEvent, + ...overrides, +}))); describe('Service/RequestService', () => { afterEach(() => sinon.restore()); - describe('test GET request getter', () => { - const testEvent = { ...getEvent }; - testEvent.queryStringParameters.test = 123; + HTTP_METHODS_WITHOUT_PAYLOADS.forEach((httpMethod) => { + describe(`HTTP ${httpMethod}`, () => { + describe('.getAll', () => { + it('should return all get parameters as an array', () => { + const event = getEvent({ httpMethod }); + event.queryStringParameters.test = 123; + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - it('should fetch a GET parameter from an AWS event', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.get('test')).toEqual(getEvent.queryStringParameters.test); - }); + expect(request.getAll()).toEqual(event.queryStringParameters); + }); + }); - it('should fetch a GET parameter from an AWS event when the request type is set', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.get('test', null, REQUEST_TYPES.GET)).toEqual(getEvent.queryStringParameters.test); - }); + describe('.get', () => { + it('should fetch a query parameter from an AWS event', () => { + const event = getEvent({ httpMethod }); + event.queryStringParameters.test = 123; + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - it('should return null from a non existent GET parameter from an AWS event', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.get('fake')).toEqual(null); - }); + expect(request.get('test')).toEqual(event.queryStringParameters.test); + }); - it('should return null from a non existent GET parameter from an AWS event when the request type is set', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.get('fake', null, REQUEST_TYPES.GET)).toEqual(null); - }); + it('should fetch a query parameter from an AWS event when the request type is set', () => { + const event = getEvent({ httpMethod }); + event.queryStringParameters.test = 123; + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - describe('.getUserBrowserAndDevice', () => { - it('should return null with `headers === undefined`', () => { - const event = { - ...testEvent, - headers: undefined, - }; - const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); + expect(request.get('test', null, httpMethod)).toEqual(event.queryStringParameters.test); + }); - expect(request.getUserBrowserAndDevice()).toEqual(null); + it(`should return null from a non existent ${httpMethod} parameter from an AWS event`, () => { + const event = getEvent({ httpMethod }); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); + + expect(request.get('fake')).toEqual(null); + }); + + it(`should return null from a non existent ${httpMethod} parameter from an AWS event when the request type is set`, () => { + const event = getEvent({ httpMethod }); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); + + expect(request.get('fake', null, httpMethod)).toEqual(null); + }); }); - it('should return null with `headers === null`', () => { - const event = { - ...testEvent, - headers: null, + describe('.validateAgainstConstraints', () => { + const constraints = { + giftaid: { + numericality: true, + }, }; - const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - expect(request.getUserBrowserAndDevice()).toEqual(null); + beforeEach(() => { + // Mute Winston + // eslint-disable-next-line no-underscore-dangle + jest.spyOn(console._stdout, 'write').mockImplementation(() => {}); + }); + + it('should resolve if there are no validation errors', async () => { + const event = getEvent({ httpMethod }); + event.queryStringParameters.giftaid = 123; + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); + + await expect(request.validateAgainstConstraints(constraints)).resolves.toEqual(undefined); + }); + + it('should return a response containing validation errors if the data provided is incorrect', async () => { + const event = getEvent({ httpMethod }); + event.queryStringParameters.giftaid = 'abc'; + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); + + await expect(request.validateAgainstConstraints(constraints)).rejects.toMatchSnapshot(); + }); }); - it('should return a prettified user agent', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.getUserBrowserAndDevice()).toEqual({ - 'browser-type': 'Safari', - 'browser-version': '9.1.1', - 'device-type': 'Other', - 'operating-system': 'Mac OS X', - 'operating-system-version': '10.11.5', + describe('.getUserBrowserAndDevice', () => { + it('should return null with `headers === undefined`', () => { + const event = getEvent({ httpMethod, headers: undefined }); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); + + expect(request.getUserBrowserAndDevice()).toEqual(null); + }); + + it('should return null with `headers === null`', () => { + const event = getEvent({ httpMethod, headers: null }); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); + + expect(request.getUserBrowserAndDevice()).toEqual(null); + }); + + it('should return a prettified user agent', () => { + const event = getEvent({ httpMethod }); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); + expect(request.getUserBrowserAndDevice()).toEqual({ + 'browser-type': 'Safari', + 'browser-version': '9.1.1', + 'device-type': 'Other', + 'operating-system': 'Mac OS X', + 'operating-system-version': '10.11.5', + }); }); }); }); }); - describe('test POST request getter', () => { - const testEvent = { ...getEvent }; - testEvent.httpMethod = 'POST'; - testEvent.headers['Content-Type'] = 'application/x-www-form-urlencoded'; - testEvent.body = 'grant_type=client_credentials&response_type=token&token_format=opaque'; + HTTP_METHODS_WITH_PAYLOADS.forEach((httpMethod) => { + const getPayloadEvent = (overrides = {}) => { + const event = getEvent({ httpMethod }); + event.headers['Content-Type'] = 'application/x-www-form-urlencoded'; + event.body = 'grant_type=client_credentials&response_type=token&token_format=opaque'; - const queryParameters = QueryString.parse(testEvent.body); + return { ...event, ...overrides }; + }; - it('should fetch a POST parameter from an AWS event', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.get('grant_type')).toEqual(queryParameters.grant_type); - }); + const queryParameters = QueryString.parse(getPayloadEvent().body); - it('should fetch a POST parameter from an AWS event when the request type is set', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.get('grant_type', null, REQUEST_TYPES.POST)).toEqual(queryParameters.grant_type); - }); + describe(`HTTP ${httpMethod}`, () => { + describe('.getAll', () => { + it('should return all post parameters as an array', () => { + const event = getPayloadEvent(); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - it('should return null from a non existent POST parameter from an AWS event', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.get('fake')).toEqual(null); - }); + expect(request.getAll()).toEqual(queryParameters); + }); + }); - it('should return null from a non existent POST parameter from an AWS event when the request type is set', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext), getEvent); - expect(request.get('fake', null, REQUEST_TYPES.POST)).toEqual(null); - }); - }); + describe('.get', () => { + it('should fetch a request body parameter from an AWS event', () => { + const event = getPayloadEvent(); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - describe('test GET request get all getter', () => { - const testEvent = { ...getEvent }; - testEvent.queryStringParameters.test = 123; - testEvent.queryStringParameters.testTwo = 123; + expect(request.get('grant_type')).toEqual(queryParameters.grant_type); + }); - it('should return all get parameters as an array', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.getAll()).toEqual(testEvent.queryStringParameters); - }); - }); + it('should fetch a request body parameter from an AWS event when the request type is set', () => { + const event = getPayloadEvent(); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - describe('test POST request get all getter', () => { - const testEvent = { ...getEvent }; - testEvent.httpMethod = 'POST'; - testEvent.headers['Content-Type'] = 'application/x-www-form-urlencoded'; - testEvent.body = 'grant_type=client_credentials&response_type=token&token_format=opaque'; + expect(request.get('grant_type', null, httpMethod)).toEqual(queryParameters.grant_type); + }); - const queryParameters = QueryString.parse(testEvent.body); + it('should return null from a non existent request body parameter from an AWS event', () => { + const event = getPayloadEvent(); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - it('should return all post parameters as an array', () => { - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - expect(request.getAll()).toEqual(queryParameters); - }); - }); + expect(request.get('fake')).toEqual(null); + }); - describe('test request validation', () => { - const testEvent = { ...getEvent }; - const constraints = { - giftaid: { - numericality: true, - }, - }; + it('should return null from a non existent request body parameter from an AWS event when the request type is set', () => { + const event = getPayloadEvent(); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext), getEvent); - describe('test validation against GET request', () => { - it('should resolve if there are no validation errors', (done) => { - testEvent.queryStringParameters.giftaid = 123; - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - - request - .validateAgainstConstraints(constraints) - .then(() => { - expect(true).toEqual(true); - done(); - }) - .catch(() => { - expect(true).toEqual(false); - done(); - }); + expect(request.get('fake', null, httpMethod)).toEqual(null); + }); }); - it('should return a response containing validation errors if the data provided is incorrect', (done) => { - testEvent.queryStringParameters.giftaid = 'abc'; - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - - request - .validateAgainstConstraints(constraints) - .then(() => { - expect(true).toEqual(false); - done(); - }) - .catch(() => { - expect(true).toEqual(true); - done(); - }); - }); - }); + describe('.validateAgainstConstraints', () => { + const constraints = { + giftaid: { + numericality: true, + }, + }; + + beforeEach(() => { + // Mute Winston + // eslint-disable-next-line no-underscore-dangle + jest.spyOn(console._stdout, 'write').mockImplementation(() => {}); + }); - describe('test validation against POST request', () => { - testEvent.httpMethod = 'POST'; - testEvent.headers['Content-Type'] = 'application/x-www-form-urlencoded'; + it('should resolve if there are no validation errors', async () => { + const event = getPayloadEvent({ body: 'giftaid=123' }); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - beforeEach(() => { - // Mute Winston - // eslint-disable-next-line no-underscore-dangle - sinon.stub(console._stdout, 'write'); - }); + await expect(request.validateAgainstConstraints(constraints)).resolves.toEqual(undefined); + }); - it('should resolve if there are no validation errors', (done) => { - testEvent.body = 'giftaid=123'; - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - - request - .validateAgainstConstraints(constraints) - .then(() => { - expect(true).toEqual(true); - done(); - }) - .catch(() => { - expect(true).toEqual(false); - done(); - }); - }); + it('should return a response containing validation errors if the data provided is incorrect', async () => { + const event = getPayloadEvent({ body: 'giftaid=abc' }); + const request = new RequestService(new DependencyInjection(CONFIGURATION, event, getContext)); - it('should return a response containing validation errors if the data provided is incorrect', (done) => { - testEvent.body = 'giftaid=abc'; - const request = new RequestService(new DependencyInjection(CONFIGURATION, testEvent, getContext)); - - request - .validateAgainstConstraints(constraints) - .then(() => { - expect(true).toEqual(false); - done(); - }) - .catch(() => { - expect(true).toEqual(true); - done(); - }); + await expect(request.validateAgainstConstraints(constraints)).rejects.toMatchSnapshot(); + }); }); }); }); describe('getAllHeaders()', () => { - const event = { ...getEvent }; + const event = getEvent(); const di = new DependencyInjection(CONFIGURATION, event, getContext); const request = new RequestService(di); it('should return all headers from the event', () => { - expect(request.getAllHeaders()).toStrictEqual(getEvent.headers); + expect(request.getAllHeaders()).toStrictEqual(event.headers); }); }); describe('getHeader()', () => { - const event = { ...getEvent }; + const event = getEvent(); const di = new DependencyInjection(CONFIGURATION, event, getContext); const request = new RequestService(di); diff --git a/tests/unit/Service/__snapshots__/Request.service.test.js.snap b/tests/unit/Service/__snapshots__/Request.service.test.js.snap new file mode 100644 index 00000000..868fb53d --- /dev/null +++ b/tests/unit/Service/__snapshots__/Request.service.test.js.snap @@ -0,0 +1,106 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Service/RequestService HTTP DELETE .validateAgainstConstraints should return a response containing validation errors if the data provided is incorrect 1`] = ` +ResponseModel { + "body": Object { + "data": Object {}, + "message": "required fields are missing", + "validation_errors": Object { + "giftaid": Array [ + "Giftaid is not a number", + ], + }, + }, + "code": 400, +} +`; + +exports[`Service/RequestService HTTP GET .validateAgainstConstraints should return a response containing validation errors if the data provided is incorrect 1`] = ` +ResponseModel { + "body": Object { + "data": Object {}, + "message": "required fields are missing", + "validation_errors": Object { + "giftaid": Array [ + "Giftaid is not a number", + ], + }, + }, + "code": 400, +} +`; + +exports[`Service/RequestService HTTP HEAD .validateAgainstConstraints should return a response containing validation errors if the data provided is incorrect 1`] = ` +ResponseModel { + "body": Object { + "data": Object {}, + "message": "required fields are missing", + "validation_errors": Object { + "giftaid": Array [ + "Giftaid is not a number", + ], + }, + }, + "code": 400, +} +`; + +exports[`Service/RequestService HTTP OPTIONS .validateAgainstConstraints should return a response containing validation errors if the data provided is incorrect 1`] = ` +ResponseModel { + "body": Object { + "data": Object {}, + "message": "required fields are missing", + "validation_errors": Object { + "giftaid": Array [ + "Giftaid is not a number", + ], + }, + }, + "code": 400, +} +`; + +exports[`Service/RequestService HTTP PATCH .validateAgainstConstraints should return a response containing validation errors if the data provided is incorrect 1`] = ` +ResponseModel { + "body": Object { + "data": Object {}, + "message": "required fields are missing", + "validation_errors": Object { + "giftaid": Array [ + "Giftaid is not a number", + ], + }, + }, + "code": 400, +} +`; + +exports[`Service/RequestService HTTP POST .validateAgainstConstraints should return a response containing validation errors if the data provided is incorrect 1`] = ` +ResponseModel { + "body": Object { + "data": Object {}, + "message": "required fields are missing", + "validation_errors": Object { + "giftaid": Array [ + "Giftaid is not a number", + ], + }, + }, + "code": 400, +} +`; + +exports[`Service/RequestService HTTP PUT .validateAgainstConstraints should return a response containing validation errors if the data provided is incorrect 1`] = ` +ResponseModel { + "body": Object { + "data": Object {}, + "message": "required fields are missing", + "validation_errors": Object { + "giftaid": Array [ + "Giftaid is not a number", + ], + }, + }, + "code": 400, +} +`;