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

Commit 89e7d08

Browse files
Merge pull request #199 from apiaryio/198-coerce-missing-real-body
feat: coerces missing "body" of the actual HTTP message
2 parents 0138366 + a3dcc14 commit 89e7d08

File tree

4 files changed

+151
-104
lines changed

4 files changed

+151
-104
lines changed

lib/units/coerce/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const otherwise = require('../../utils/otherwise');
44
const coercionMap = {
55
method: otherwise(''),
66
uri: otherwise(''),
7-
headers: otherwise({})
7+
headers: otherwise({}),
8+
body: otherwise('')
89
};
910

1011
// Coercion is strict by default, meaning it would populate

lib/units/validateBody.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ function getBodySchemaType(bodySchema) {
103103
jph.parse(bodySchema);
104104
return [null, jsonSchemaType];
105105
} catch (exception) {
106-
const error = `Can't validate: expected body JSON Schema is not a parseable JSON:\n${
107-
exception.message
108-
}`;
106+
const error = `Can't validate: expected body JSON Schema is not a parseable JSON:\n${exception.message}`;
109107

110108
return [error, null];
111109
}
@@ -157,10 +155,14 @@ function getBodyValidator(realType, expectedType) {
157155
*/
158156
function validateBody(expected, real) {
159157
const errors = [];
160-
const bodyType = typeof real.body;
158+
const realBodyType = typeof real.body;
159+
const hasEmptyRealBody = real.body === '';
161160

162-
if (bodyType !== 'string') {
163-
throw new Error(`Expected HTTP body to be a String, but got: ${bodyType}`);
161+
// Throw when user input for real body is not a string.
162+
if (realBodyType !== 'string') {
163+
throw new Error(
164+
`Expected HTTP body to be a string, but got: ${realBodyType}`
165+
);
164166
}
165167

166168
const [realTypeError, realType] = getBodyType(
@@ -197,9 +199,22 @@ function validateBody(expected, real) {
197199
: getBodyValidator(realType, expectedType);
198200

199201
if (validatorError) {
200-
errors.push({
201-
message: validatorError
202-
});
202+
// In case determined media types mismtach, check if the real is not missing.
203+
// Keep in mind the following scenarios:
204+
// 1. Expected '', and got '' (TextDiff/TextDiff, valid)
205+
// 2. Expected {...}, but got '' (Json/TextDiff, invalid, produces "missing real body" error)
206+
// 3. Expected {...}, but got "foo" (Json/TextDiff, invalid, produces types mismatch error).
207+
if (expected.body !== '' && hasEmptyRealBody) {
208+
errors.push({
209+
message: `Expected "body" of "${mediaTyper.format(
210+
expectedType
211+
)}" media type, but actual "body" is missing.`
212+
});
213+
} else {
214+
errors.push({
215+
message: validatorError
216+
});
217+
}
203218
}
204219

205220
const usesJsonSchema = ValidatorClass && ValidatorClass.name === 'JsonSchema';

test/integration/validate.test.js

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
const { assert } = require('chai');
22
const { validate } = require('../../lib/validate');
33

4+
const isValid = (result, expectedValid = true) => {
5+
it(`sets "isValid" to ${JSON.stringify(expectedValid)}`, () => {
6+
assert.propertyVal(result, 'isValid', expectedValid);
7+
});
8+
};
9+
410
const validator = (obj, expected) => {
511
it(`has "${expected}" validator`, () => {
612
assert.propertyVal(obj, 'validator', expected);
@@ -32,26 +38,22 @@ describe('validate', () => {
3238
};
3339
const result = validate(request, request);
3440

35-
it('returns validation result object', () => {
36-
assert.isObject(result);
37-
});
38-
39-
it('has "isValid" set to true', () => {
40-
assert.propertyVal(result, 'isValid', true);
41-
});
41+
isValid(result);
4242

4343
it('contains all validatable keys', () => {
4444
assert.hasAllKeys(result.fields, ['method', 'headers', 'body']);
4545
});
4646

4747
describe('method', () => {
48+
isValid(result.fields.method);
4849
validator(result.fields.method, null);
4950
expectedType(result.fields.method, 'text/vnd.apiary.method');
5051
realType(result.fields.method, 'text/vnd.apiary.method');
5152
noErrors(result.fields.method);
5253
});
5354

5455
describe('headers', () => {
56+
isValid(result.fields.headers);
5557
validator(result.fields.headers, 'HeadersJsonExample');
5658
expectedType(
5759
result.fields.headers,
@@ -65,6 +67,7 @@ describe('validate', () => {
6567
});
6668

6769
describe('body', () => {
70+
isValid(result.fields.body);
6871
validator(result.fields.body, 'JsonExample');
6972
expectedType(result.fields.body, 'application/json');
7073
realType(result.fields.body, 'application/json');
@@ -88,19 +91,14 @@ describe('validate', () => {
8891
}
8992
);
9093

91-
it('returns validation result object', () => {
92-
assert.isObject(result);
93-
});
94-
95-
it('has "isValid" set to false', () => {
96-
assert.propertyVal(result, 'isValid', false);
97-
});
94+
isValid(result, false);
9895

9996
it('contains all validatable keys', () => {
10097
assert.hasAllKeys(result.fields, ['method', 'headers', 'body']);
10198
});
10299

103100
describe('method', () => {
101+
isValid(result.fields.method, false);
104102
validator(result.fields.method, null);
105103
expectedType(result.fields.method, 'text/vnd.apiary.method');
106104
realType(result.fields.method, 'text/vnd.apiary.method');
@@ -121,6 +119,7 @@ describe('validate', () => {
121119
});
122120

123121
describe('headers', () => {
122+
isValid(result.fields.headers);
124123
validator(result.fields.headers, 'HeadersJsonExample');
125124
expectedType(
126125
result.fields.headers,
@@ -134,6 +133,7 @@ describe('validate', () => {
134133
});
135134

136135
describe('body', () => {
136+
isValid(result.fields.body, false);
137137
validator(result.fields.body, 'JsonExample');
138138
expectedType(result.fields.body, 'application/json');
139139
realType(result.fields.body, 'application/json');
@@ -177,13 +177,15 @@ describe('validate', () => {
177177
});
178178

179179
describe('statusCode', () => {
180+
isValid(result.fields.statusCode);
180181
validator(result.fields.statusCode, 'TextDiff');
181182
expectedType(result.fields.statusCode, 'text/vnd.apiary.status-code');
182183
realType(result.fields.statusCode, 'text/vnd.apiary.status-code');
183184
noErrors(result.fields.statusCode);
184185
});
185186

186187
describe('headers', () => {
188+
isValid(result.fields.headers);
187189
validator(result.fields.headers, 'HeadersJsonExample');
188190
expectedType(
189191
result.fields.headers,
@@ -197,6 +199,7 @@ describe('validate', () => {
197199
});
198200

199201
describe('body', () => {
202+
isValid(result.fields.body);
200203
validator(result.fields.body, 'JsonExample');
201204
expectedType(result.fields.body, 'application/json');
202205
realType(result.fields.body, 'application/json');
@@ -219,19 +222,14 @@ describe('validate', () => {
219222
};
220223
const result = validate(expectedResponse, realResponse);
221224

222-
it('returns validation result object', () => {
223-
assert.isObject(result);
224-
});
225-
226-
it('has "isValid" as false', () => {
227-
assert.propertyVal(result, 'isValid', false);
228-
});
225+
isValid(result, false);
229226

230227
it('contains all validatable keys', () => {
231228
assert.hasAllKeys(result.fields, ['statusCode', 'headers']);
232229
});
233230

234231
describe('statusCode', () => {
232+
isValid(result.fields.statusCode, false);
235233
validator(result.fields.statusCode, 'TextDiff');
236234
expectedType(result.fields.statusCode, 'text/vnd.apiary.status-code');
237235
realType(result.fields.statusCode, 'text/vnd.apiary.status-code');
@@ -252,6 +250,7 @@ describe('validate', () => {
252250
});
253251

254252
describe('headers', () => {
253+
isValid(result.fields.headers, false);
255254
validator(result.fields.headers, 'HeadersJsonExample');
256255
expectedType(
257256
result.fields.headers,
@@ -291,26 +290,22 @@ describe('validate', () => {
291290
}
292291
);
293292

294-
it('returns validation result object', () => {
295-
assert.isObject(result);
296-
});
297-
298-
it('has "isValid" as false', () => {
299-
assert.propertyVal(result, 'isValid', false);
300-
});
293+
isValid(result, false);
301294

302295
it('contains all validatable keys', () => {
303296
assert.hasAllKeys(result.fields, ['statusCode', 'headers']);
304297
});
305298

306299
describe('statusCode', () => {
300+
isValid(result.fields.statusCode);
307301
validator(result.fields.statusCode, 'TextDiff');
308302
expectedType(result.fields.statusCode, 'text/vnd.apiary.status-code');
309303
realType(result.fields.statusCode, 'text/vnd.apiary.status-code');
310304
noErrors(result.fields.statusCode);
311305
});
312306

313307
describe('headers', () => {
308+
isValid(result.fields.headers, false);
314309
validator(result.fields.headers, 'HeadersJsonExample');
315310
expectedType(
316311
result.fields.headers,
@@ -348,41 +343,46 @@ describe('validate', () => {
348343
describe('always validates expected properties', () => {
349344
const result = validate(
350345
{
346+
method: 'POST',
351347
statusCode: 200,
352348
headers: {
353349
'Content-Type': 'application/json'
354350
},
355351
body: '{ "foo": "bar" }'
356352
},
357353
{
358-
body: 'doe'
354+
method: 'PUT'
359355
}
360356
);
361357

362-
it('has "isValid" as false', () => {
363-
assert.propertyVal(result, 'isValid', false);
364-
});
358+
isValid(result, false);
365359

366360
it('contains all validatable keys', () => {
367-
assert.hasAllKeys(result.fields, ['statusCode', 'headers', 'body']);
361+
assert.hasAllKeys(result.fields, [
362+
'method',
363+
'statusCode',
364+
'headers',
365+
'body'
366+
]);
368367
});
369368

370369
describe('for properties present in both expected and real', () => {
371-
describe('body', () => {
372-
validator(result.fields.body, null);
373-
expectedType(result.fields.body, 'application/json');
374-
realType(result.fields.body, 'text/plain');
370+
describe('method', () => {
371+
isValid(result.fields.method, false);
372+
validator(result.fields.method, null);
373+
expectedType(result.fields.method, 'text/vnd.apiary.method');
374+
realType(result.fields.method, 'text/vnd.apiary.method');
375375

376376
describe('produces an error', () => {
377377
it('exactly one error', () => {
378-
assert.lengthOf(result.fields.body.errors, 1);
378+
assert.lengthOf(result.fields.method.errors, 1);
379379
});
380380

381381
it('has explanatory message', () => {
382382
assert.propertyVal(
383-
result.fields.body.errors[0],
383+
result.fields.method.errors[0],
384384
'message',
385-
`Can't validate real media type 'text/plain' against expected media type 'application/json'.`
385+
'Expected "method" field to equal "POST", but got "PUT".'
386386
);
387387
});
388388
});
@@ -391,6 +391,7 @@ describe('validate', () => {
391391

392392
describe('for properties present in expected, but not in real', () => {
393393
describe('statusCode', () => {
394+
isValid(result.fields.statusCode, false);
394395
validator(result.fields.statusCode, 'TextDiff');
395396
expectedType(result.fields.statusCode, 'text/vnd.apiary.status-code');
396397
realType(result.fields.statusCode, 'text/vnd.apiary.status-code');
@@ -411,6 +412,7 @@ describe('validate', () => {
411412
});
412413

413414
describe('headers', () => {
415+
isValid(result.fields.headers, false);
414416
validator(result.fields.headers, 'HeadersJsonExample');
415417
expectedType(
416418
result.fields.headers,
@@ -435,6 +437,27 @@ describe('validate', () => {
435437
});
436438
});
437439
});
440+
441+
describe('body', () => {
442+
isValid(result.fields.body, false);
443+
validator(result.fields.body, null);
444+
expectedType(result.fields.body, 'application/json');
445+
realType(result.fields.body, 'text/plain');
446+
447+
describe('produces an error', () => {
448+
it('exactly one error', () => {
449+
assert.lengthOf(result.fields.body.errors, 1);
450+
});
451+
452+
it('has explanatory message', () => {
453+
assert.propertyVal(
454+
result.fields.body.errors[0],
455+
'message',
456+
'Expected "body" of "application/json" media type, but actual "body" is missing.'
457+
);
458+
});
459+
});
460+
});
438461
});
439462
});
440463
});

0 commit comments

Comments
 (0)