Skip to content
This repository has been archived by the owner on Nov 8, 2024. It is now read-only.

Commit

Permalink
feat: validates query parameters (validateURI)
Browse files Browse the repository at this point in the history
  • Loading branch information
artem-zakharchenko committed Jun 11, 2019
1 parent 00ac00a commit ac08597
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 53 deletions.
50 changes: 48 additions & 2 deletions lib/units/validateURI.js
Original file line number Diff line number Diff line change
@@ -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}".`
});
}

Expand Down
246 changes: 195 additions & 51 deletions test/unit/units/validateURI.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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".'
);
});
});
});
});
});
Expand Down

0 comments on commit ac08597

Please sign in to comment.