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

Commit 272a691

Browse files
refactor: adds "validateBody" unit
1 parent 35d86c9 commit 272a691

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
const { assert } = require('chai');
2+
const validateBody = require('../../units/validateBody');
3+
4+
describe.only('validateBody', () => {
5+
describe('when given unsupported body type', () => {
6+
const scenarios = [
7+
{ name: 'number', value: 5 },
8+
{ name: 'array', value: ['foo', 'bar'] },
9+
{ name: 'object', value: { foo: 'bar' } },
10+
{ name: 'null', value: null },
11+
{ name: 'undefined', value: undefined }
12+
];
13+
14+
scenarios.forEach(({ name, value }) => {
15+
it(`errors when given ${name}`, () => {
16+
assert.throw(() => validateBody({ body: value }, { body: value }));
17+
});
18+
});
19+
});
20+
21+
describe('when given supported body type', () => {
22+
describe('with explicit Content-Type header', () => {
23+
describe('application/json', () => {
24+
describe('and the body type matches content-type', () => {
25+
const res = validateBody(
26+
{
27+
body: '{ "foo": "bar" }',
28+
headers: { 'Content-Type': 'application/json' }
29+
},
30+
{ body: '{ "foo": "bar" }' }
31+
);
32+
33+
it('has proper real type', () => {
34+
assert.propertyVal(res, 'realType', 'application/json');
35+
});
36+
});
37+
38+
describe('and the body does not match content-type', () => {
39+
const res = validateBody(
40+
{
41+
body: 'foo',
42+
headers: { 'content-type': 'application/json' }
43+
},
44+
{ body: '{ "foo": "bar" }' }
45+
);
46+
47+
it('uses plain/text as real type', () => {
48+
assert.propertyVal(res, 'realType', 'text/plain');
49+
});
50+
51+
describe('produces error', () => {
52+
it('has proper severity', () => {
53+
assert.propertyVal(res.results[0], 'severity', 'error');
54+
});
55+
it('has explanatory message', () => {
56+
assert.match(
57+
res.results[0].message,
58+
/^Real body 'Content-Type' header is 'application\/json' but body is not a parseable JSON:/
59+
);
60+
});
61+
});
62+
});
63+
});
64+
});
65+
66+
describe('without explicit Content-Type header', () => {
67+
describe('text/plain', () => {
68+
describe('and the bodies match', () => {
69+
const res = validateBody({ body: 'foo' }, { body: 'foo' });
70+
71+
it('has proper real type', () => {
72+
assert.propertyVal(res, 'realType', 'text/plain');
73+
});
74+
});
75+
76+
describe('and the bodies do not match', () => {
77+
const res = validateBody({ body: 'foo ' }, { body: 'bar' });
78+
79+
it('has proper real type', () => {
80+
assert.propertyVal(res, 'realType', 'text/plain');
81+
});
82+
});
83+
});
84+
85+
describe('application/json', () => {
86+
describe('and the bodies match', () => {
87+
const res = validateBody(
88+
{ body: '{ "foo": "bar" }' },
89+
{ body: '{ "foo": "bar" }' }
90+
);
91+
92+
it('has proper real type', () => {
93+
assert.propertyVal(res, 'realType', 'application/json');
94+
});
95+
});
96+
97+
describe('and the bodies do not match', () => {
98+
const res = validateBody(
99+
{ body: '{ "foo": "bar" }' },
100+
{ body: '{ "bar": null }' }
101+
);
102+
103+
it('has proper real type', () => {
104+
assert.propertyVal(res, 'realType', 'application/json');
105+
});
106+
});
107+
});
108+
});
109+
});
110+
});

lib/api/units/validateBody.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const jph = require('json-parse-helpfulerror');
2+
const mediaTyper = require('media-typer');
3+
const contentTypeUtils = require('content-type');
4+
5+
/**
6+
* Determines if a given content type header contains JSON.
7+
* @param {string} contentType
8+
* @returns {boolean}
9+
*/
10+
function isJsonContentType(contentType) {
11+
if (!contentType) {
12+
return false;
13+
}
14+
15+
try {
16+
const { type } = contentTypeUtils.parse(`${contentType}`);
17+
const parsed = mediaTyper.parse(type);
18+
return (
19+
(parsed.type === 'application' && parsed.subtype === 'json') ||
20+
parsed.suffix === 'json'
21+
);
22+
} catch (error) {
23+
// The Content-Type value is basically a user input, it can be any
24+
// kind of rubbish, and it is neither this function's nor Gavel's problem
25+
// if it's invalid
26+
return false;
27+
}
28+
}
29+
30+
/**
31+
* Validates given bodies of transaction elements.
32+
* @param {Object} real
33+
* @param {Object} expected
34+
*/
35+
function validateBody(real, expected) {
36+
const { headers: realHeaders, body: realBody } = real;
37+
const { body: expectedBody } = expected;
38+
const bodyType = typeof realBody;
39+
const contentType = realHeaders && realHeaders['content-type'];
40+
const hasJsonContentType = isJsonContentType(contentType);
41+
let realType = null;
42+
const results = [];
43+
44+
console.log(expectedBody);
45+
46+
if (bodyType !== 'string') {
47+
throw new Error(`Expected HTTP body to be a String, but got: ${bodyType}`);
48+
}
49+
50+
try {
51+
jph.parse(realBody);
52+
realType = hasJsonContentType ? contentType : 'application/json';
53+
} catch (error) {
54+
realType = 'text/plain';
55+
56+
if (hasJsonContentType) {
57+
results.push({
58+
message: `\
59+
Real body 'Content-Type' header is '${contentType}' \
60+
but body is not a parseable JSON:
61+
${error.message}\
62+
`,
63+
severity: 'error'
64+
});
65+
}
66+
}
67+
68+
return {
69+
validator: null,
70+
realType,
71+
expectedType: null,
72+
rawData: null,
73+
results
74+
};
75+
}
76+
77+
module.exports = validateBody;

0 commit comments

Comments
 (0)