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

Commit

Permalink
test: adds granular unit tests for "validateBody"
Browse files Browse the repository at this point in the history
  • Loading branch information
artem-zakharchenko committed May 15, 2019
1 parent 9c50d2f commit 8630403
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { assert } = require('chai');
const validateBody = require('../../units/validateBody');
const { validateBody } = require('../../../units/validateBody');

describe('validateBody', () => {
describe('when given unsupported body type', () => {
Expand Down
48 changes: 48 additions & 0 deletions lib/api/test/unit/units/validateBody/getBodySchemaType.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const { assert } = require('chai');
const mediaTyper = require('media-typer');
const { getBodySchemaType } = require('../../../../units/validateBody');

describe('getBodySchemaType', () => {
describe('when given non-string schema', () => {
// ...
});

describe('when given json schema', () => {
describe('that contains object', () => {
const res = getBodySchemaType('{ "foo": "bar" }');

it('returns no errors', () => {
assert.isNull(res[0]);
});

it('returns "application/schema+json" media type', () => {
assert.deepEqual(res[1], mediaTyper.parse('application/schema+json'));
});
});

describe('that contains array', () => {
const res = getBodySchemaType('[1, 2, 3]');

it('returns no errors', () => {
assert.isNull(res[0]);
});

it('returns "application/schema+json" media type', () => {
assert.deepEqual(res[1], mediaTyper.parse('application/schema+json'));
});
});
});

describe('when given non-json schema', () => {
const res = getBodySchemaType('foo');

it('returns parsing error', () => {
assert.match(
res[0],
/^Can't validate. Expected body JSON Schema is not a parseable JSON:/
);
});

it('returns no media type', () => [assert.isNull(res[1])]);
});
});
78 changes: 78 additions & 0 deletions lib/api/test/unit/units/validateBody/getBodyType.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const { assert } = require('chai');
const mediaTyper = require('media-typer');
const { getBodyType } = require('../../../../units/validateBody');

const jsonTypes = ['application/json', 'application/schema+json'];
const nonJsonTypes = ['text/plain'];

describe('getBodyType', () => {
describe('when given json-like content type', () => {
jsonTypes.forEach((jsonType) => {
describe(`when given "${jsonType}" content type`, () => {
describe('and parsable json body', () => {
const res = getBodyType('{ "foo": "bar" }', jsonType);

it('returns no errors', () => {
assert.isNull(res[0]);
});

it(`returns "${jsonType}" media type`, () => {
assert.deepEqual(res[1], mediaTyper.parse(jsonType));
});
});

describe('and non-parsable json body', () => {
const res = getBodyType('abc', jsonType);

it('returns parsing error', () => {
assert.match(
res[0],
new RegExp(
/* eslint-disable no-useless-escape */
`^Real body 'Content-Type' header is \'${jsonType.replace(
/(\/|\+)/g,
'\\$1'
)}\' but body is not a parseable JSON:`
/* eslint-enable no-useless-escape */
)
);
});

it('uses "text/plain" as fallback media type', () => {
assert.deepEqual(res[1], mediaTyper.parse('text/plain'));
});
});
});
});
});

describe('when given non-json content type', () => {
nonJsonTypes.forEach((nonJsonType) => {
describe(`when given "${nonJsonType}" content type`, () => {
describe('and parsable json body', () => {
const res = getBodyType('{ "foo": "bar" }', nonJsonType);

it('returns no errors', () => {
assert.isNull(res[0]);
});

it('coerces to "application/json" media type', () => {
assert.deepEqual(res[1], mediaTyper.parse('application/json'));
});
});

describe('and a non-json body', () => {
const res = getBodyType('abc', nonJsonType);

it('returns no errors', () => {
assert.isNull(res[0]);
});

it(`returns "${nonJsonType}" media type`, () => {
assert.deepEqual(res[1], mediaTyper.parse(nonJsonType));
});
});
});
});
});
});
81 changes: 81 additions & 0 deletions lib/api/test/unit/units/validateBody/getBodyValidator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const { assert } = require('chai');
const mediaTyper = require('media-typer');
const { getBodyValidator } = require('../../../../units/validateBody');

const getMediaTypes = (real, expected) => {
return [real, expected].map(mediaTyper.parse);
};

describe('getBodyValidator', () => {
const knownContentTypes = [
/* Known content types */
{
contentTypes: ['application/json', 'application/json'],
expectedValidator: 'JsonExample'
},
{
contentTypes: ['application/json', 'application/schema+json'],
expectedValidator: 'JsonSchema'
},
{
contentTypes: ['text/plain', 'text/plain'],
expectedValidator: 'TextDiff'
}
];

const unknownContentTypes = [
{
contentTypes: ['application/xml', 'application/xml']
},
{
contentTypes: ['text/html', 'application/xml']
}
];

describe('when given known media type', () => {
knownContentTypes.forEach(({ contentTypes, expectedValidator }) => {
const [realContentType, expectedContentType] = contentTypes;
const [real, expected] = getMediaTypes(
realContentType,
expectedContentType
);

describe(`${realContentType} + ${expectedContentType}`, () => {
const [error, validator] = getBodyValidator(real, expected);

it('returns no error', () => {
assert.isNull(error);
});

it(`returns "${expectedValidator}" validator`, () => {
assert.equal(validator.name, expectedValidator);
});
});
});
});

describe('when given unknown media type', () => {
unknownContentTypes.forEach(({ contentTypes }) => {
const [realContentType, expectedContentType] = contentTypes;
const [real, expected] = getMediaTypes(
realContentType,
expectedContentType
);

describe(`${realContentType} + ${expectedContentType}`, () => {
const [error, validator] = getBodyValidator(real, expected);

it('has explanatory error', () => {
assert.equal(
error,
`Can't validate real media type '${realContentType}' against expected media type '${expectedContentType}'.`
);
});

it('returns null validator', () => {
assert.isNull(validator);
});
});
});
});
});
29 changes: 29 additions & 0 deletions lib/api/test/unit/units/validateBody/isJson.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const { assert } = require('chai');
const mediaTyper = require('media-typer');
const { isJson } = require('../../../../units/validateBody');

describe('isJson', () => {
describe('returns true', () => {
it('when given valid json media type', () => {
assert.isTrue(isJson(mediaTyper.parse('application/json')));
});

it('when given json-compatible media type', () => {
assert.isTrue(isJson(mediaTyper.parse('application/schema+json')));
});
});

describe('returns false', () => {
it('when given non-json media type', () => {
assert.isFalse(isJson(mediaTyper.parse('text/plain')));
});

it('when given rubbish', () => {
assert.isFalse(isJson('abc'));
});

it('when given null', () => {
assert.isFalse(isJson(null));
});
});
});
28 changes: 28 additions & 0 deletions lib/api/test/unit/units/validateBody/isJsonContentType.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { assert } = require('chai');
const { isJsonContentType } = require('../../../../units/validateBody');

describe('isJsonContentType', () => {
describe('returns true', () => {
const jsonTypes = ['application/json', 'application/schema+json'];

jsonTypes.forEach((contentType) => {
it(`when given ${contentType}`, () => {
assert.isTrue(isJsonContentType(contentType));
});
});
});

describe('returns false', () => {
const nonJsonTypes = ['application/xml', 'text/plain'];

nonJsonTypes.forEach((contentType) => {
it(`when given ${contentType}`, () => {
assert.isFalse(isJsonContentType(contentType));
});
});

it('when given rubbish', () => {
assert.isFalse(isJsonContentType('foo'));
});
});
});
25 changes: 25 additions & 0 deletions lib/api/test/unit/units/validateBody/isJsonSchema.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { assert } = require('chai');
const mediaTyper = require('media-typer');
const { isJsonSchema } = require('../../../../units/validateBody');

describe('isJsonSchema', () => {
describe('returns true', () => {
it('when given a json schema media type', () => {
assert.isTrue(isJsonSchema(mediaTyper.parse('application/schema+json')));
});
});

describe('returns false', () => {
it('when given json media type', () => {
assert.isFalse(isJsonSchema(mediaTyper.parse('application/json')));
});

it('when given rubbish', () => {
assert.isFalse(isJsonSchema('abc'));
});

it('when given null', () => {
assert.isFalse(isJsonSchema(null));
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { assert } = require('chai');
const validateHeaders = require('../../units/validateHeaders');
const validateHeaders = require('../../../units/validateHeaders');

describe('validateHeaders', () => {
describe('given matching headers', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { assert } = require('chai');
const validateStatusCode = require('../../units/validateStatusCode');
const validateStatusCode = require('../../../units/validateStatusCode');

describe('validateStatusCode', () => {
describe('given matching status codes', () => {
Expand Down
33 changes: 28 additions & 5 deletions lib/api/units/validateBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ function isPlainText(mediaType) {
}

function isJson(mediaType) {
if (!mediaType) {
return false;
}

return (
(mediaType.type === 'application' && mediaType.subtype === 'json') ||
mediaType.suffix === 'json'
);
}

function isJsonSchema(mediaType) {
if (!mediaType) {
return false;
}

return (
mediaType.type === 'application' &&
mediaType.subtype === 'schema' &&
Expand Down Expand Up @@ -105,10 +113,8 @@ function getBodySchemaType(bodySchema) {
}

try {
const parsed = jph.parse(bodySchema);
if (typeof parsed === 'object') {
return [null, jsonSchemaType];
}
jph.parse(bodySchema);
return [null, jsonSchemaType];
} catch (exception) {
const error = `Can't validate. Expected body JSON Schema is not a parseable JSON:\n${
exception.message
Expand Down Expand Up @@ -151,6 +157,13 @@ function getBodyValidator(realType, expectedType) {
const error = `Can't validate real media type '${mediaTyper.format(
realType
)}' against expected media type '${mediaTyper.format(expectedType)}'.`;

/**
* Shouldn't we fallback to "TextDiff" validator on unknown content types?
* Unless content type is invalid, it can be at least text diff'ed.
* Considering that the input of this function is an already parsed content type,
* it's impossible for it to be invalid.

This comment has been minimized.

Copy link
@honzajavorek

honzajavorek May 15, 2019

Contributor

Hmm. I'm thinking - if we already have a content-type mismatch, is it worth diffing the body? At this point Gavel is 100% sure the two things it got to compare are not matching. It can fail fast.

*/
return [error, null];
}

Expand Down Expand Up @@ -232,4 +245,14 @@ function validateBody(real, expected) {
};
}

module.exports = validateBody;
module.exports = {
validateBody,

isJson,
isJsonSchema,
isJsonContentType,
parseContentType,
getBodyType,
getBodySchemaType,
getBodyValidator
};

0 comments on commit 8630403

Please sign in to comment.