diff --git a/lib/units/validateURI.js b/lib/units/validateURI.js index 34015d7d..c1044db1 100644 --- a/lib/units/validateURI.js +++ b/lib/units/validateURI.js @@ -1,14 +1,60 @@ +const deepEqual = require('deep-equal'); + const APIARY_URI_TYPE = 'text/vnd.apiary.uri'; +/** + * Parses a given query string into an Object. + * @param {string} queryString + */ +const parseQueryString = (queryString) => { + if (!queryString) { + return {}; + } + + return queryString.split('&').reduce((acc, paramString) => { + const [paramName, paramValue] = paramString.split('='); + const nextValue = Object.prototype.hasOwnProperty.call(acc, paramName) + ? [].concat(acc[paramName], paramValue) + : paramValue; + + return { + ...acc, + [paramName]: nextValue + }; + }, {}); +}; + +/** + * @param {string} uri + */ +const parseURI = (uri) => { + const parsed = /(\w+)(\?(.+))?/.exec(uri) || []; + const hostname = parsed[1]; + const queryString = parsed[3]; + + return { + hostname, + query: parseQueryString(queryString) + }; +}; + const validateURI = (expected, real) => { const { uri: expectedURI } = expected; const { uri: realURI } = real; + + // Parses URI into Objects to deal with + // the order of query parameters. + const parsedExpected = parseURI(expectedURI); + const parsedReal = parseURI(realURI); + + // Note the different order of arguments between + // "validateURI" and "deepEqual". + const isValid = deepEqual(parsedReal, parsedExpected); const errors = []; - const isValid = expectedURI === realURI; if (!isValid) { errors.push({ - message: `Expected "uri" field to equal "${expectedURI}", but got "${realURI}".` + message: `Expected "uri" field to equal "${expectedURI}", but got: "${realURI}".` }); } diff --git a/test/unit/units/validateURI.test.js b/test/unit/units/validateURI.test.js index 7e67bfa5..ca5299e6 100644 --- a/test/unit/units/validateURI.test.js +++ b/test/unit/units/validateURI.test.js @@ -3,73 +3,217 @@ const { validateURI } = require('../../../lib/units/validateURI'); describe('validateURI', () => { describe('given matching URI', () => { - const result = validateURI( - { - uri: '/dashboard' - }, - { - uri: '/dashboard' - } - ); - - it('marks field as valid', () => { - assert.propertyVal(result, 'isValid', true); - }); + describe('without parameters', () => { + const result = validateURI( + { + uri: '/dashboard' + }, + { + uri: '/dashboard' + } + ); - it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); - }); + it('marks field as valid', () => { + assert.propertyVal(result, 'isValid', true); + }); - it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); - }); + it('has "null" validator', () => { + assert.propertyVal(result, 'validator', null); + }); + + it('has "text/vnd.apiary.uri" real type', () => { + assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + }); - it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + it('has "text/vnd.apiary.uri" expected type', () => { + assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + }); + + it('has no errors', () => { + assert.lengthOf(result.errors, 0); + }); }); - it('has no errors', () => { - assert.lengthOf(result.errors, 0); + describe('with parameters', () => { + describe('with exact parameters', () => { + const result = validateURI( + { + uri: '/animals?type=cats' + }, + { + uri: '/animals?type=cats' + } + ); + + it('marks field as valid', () => { + assert.propertyVal(result, 'isValid', true); + }); + + it('has "null" validator', () => { + assert.propertyVal(result, 'validator', null); + }); + + it('has "text/vnd.apiary.uri" real type', () => { + assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + }); + + it('has "text/vnd.apiary.uri" expected type', () => { + assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + }); + + it('has no errors', () => { + assert.lengthOf(result.errors, 0); + }); + }); + + describe('with exact parameters in different order', () => { + // Order of different query params must not matter + const result = validateURI( + { + uri: '/animals?type=cats&size=10' + }, + { + uri: '/animals?size=10&type=cats' + } + ); + + it('mark field as valid', () => { + assert.propertyVal(result, 'isValid', true); + }); + + it('has "null" validator', () => { + assert.propertyVal(result, 'validator', null); + }); + + it('has "text/vnd.apiary.uri" real type', () => { + assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + }); + + it('has "text/vnd.apiary.uri" expected type', () => { + assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + }); + + it('has no errors', () => { + assert.lengthOf(result.errors, 0); + }); + }); + + describe('with multiple parameters in exact order', () => { + // Order of the multiple same query params matters + const result = validateURI( + { + uri: '/animals?type=cats&type=dogs' + }, + { + uri: '/animals?type=cats&type=dogs' + } + ); + + it('marks field as valid', () => { + assert.propertyVal(result, 'isValid', true); + }); + + it('has "null" validator', () => { + assert.propertyVal(result, 'validator', null); + }); + + it('has "text/vnd.apiary.uri" real type', () => { + assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + }); + + it('has "text/vnd.apiary.uri" expected type', () => { + assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + }); + + it('has no errors', () => { + assert.lengthOf(result.errors, 0); + }); + }); }); }); describe('given non-matching URI', () => { - const result = validateURI( - { - uri: '/dashboard' - }, - { - uri: '/profile' - } - ); - - it('marks field as invalid', () => { - assert.propertyVal(result, 'isValid', false); - }); + describe('without parameters', () => { + const result = validateURI( + { + uri: '/dashboard' + }, + { + uri: '/profile' + } + ); - it('has "null" validator', () => { - assert.propertyVal(result, 'validator', null); - }); + it('marks field as invalid', () => { + assert.propertyVal(result, 'isValid', false); + }); - it('has "text/vnd.apiary.uri" real type', () => { - assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); - }); + it('has "null" validator', () => { + assert.propertyVal(result, 'validator', null); + }); - it('has "text/vnd.apiary.uri" expected type', () => { - assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); - }); + it('has "text/vnd.apiary.uri" real type', () => { + assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + }); + + it('has "text/vnd.apiary.uri" expected type', () => { + assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + }); + + describe('produces an error', () => { + it('exactly one error', () => { + assert.lengthOf(result.errors, 1); + }); - describe('produces an error', () => { - it('exactly one error', () => { - assert.lengthOf(result.errors, 1); + it('has explanatory message', () => { + assert.propertyVal( + result.errors[0], + 'message', + 'Expected "uri" field to equal "/dashboard", but got: "/profile".' + ); + }); }); + }); - it('has explanatory message', () => { - assert.propertyVal( - result.errors[0], - 'message', - 'Expected "uri" field to equal "/dashboard", but got "/profile".' + describe('with parameters', () => { + describe('with multiple parameters in wrong order', () => { + const result = validateURI( + { + uri: '/zoo?type=cats&type=dogs' + }, + { + uri: '/zoo?type=dogs&type=cats' + } ); + + it('marks field is invalid', () => { + assert.propertyVal(result, 'isValid', false); + }); + + it('has "null" validator', () => { + assert.propertyVal(result, 'validator', null); + }); + + it('has "text/vnd.apiary.uri" real type', () => { + assert.propertyVal(result, 'realType', 'text/vnd.apiary.uri'); + }); + + it('has "text/vnd.apiary.uri" expected type', () => { + assert.propertyVal(result, 'expectedType', 'text/vnd.apiary.uri'); + }); + + describe('produces an error', () => { + it('exactly one error', () => { + assert.lengthOf(result.errors, 1); + }); + + it('has explanatory message', () => { + assert.propertyVal( + result.errors[0], + 'message', + 'Expected "uri" field to equal "/zoo?type=cats&type=dogs", but got: "/zoo?type=dogs&type=cats".' + ); + }); + }); }); }); });