Skip to content

Commit 7be76a4

Browse files
committed
fix(rest): add tests for request validation per media type
See #1494
1 parent 55d7373 commit 7be76a4

File tree

2 files changed

+76
-8
lines changed

2 files changed

+76
-8
lines changed

packages/rest/test/acceptance/validation/validation.acceptance.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,14 @@ describe('Validation at REST level', () => {
4242

4343
const PRODUCT_SPEC = jsonToSchemaObject(getJsonSchema(Product));
4444

45+
// Add a schema that requires `description`
46+
const PRODUCT_SPEC_WITH_DESCRIPTION = jsonToSchemaObject(
47+
getJsonSchema(Product),
48+
);
49+
PRODUCT_SPEC_WITH_DESCRIPTION.required!.push('description');
50+
4551
// This is the standard use case that most LB4 applications should use.
46-
// The request body specification is infered from a decorated model class.
52+
// The request body specification is inferred from a decorated model class.
4753
context('for request body specified via model definition', () => {
4854
class ProductController {
4955
@post('/products')
@@ -87,7 +93,7 @@ describe('Validation at REST level', () => {
8793
});
8894
});
8995

90-
// A request body schema can be provied explicitly by the user
96+
// A request body schema can be provided explicitly by the user
9197
// as an inlined content[type].schema property.
9298
context('for fully-specified request body', () => {
9399
class ProductControllerWithFullSchema {
@@ -111,6 +117,37 @@ describe('Validation at REST level', () => {
111117
serverRejectsRequestWithMissingRequiredValues());
112118
});
113119

120+
context('for different schemas per media type', () => {
121+
let spec = aBodySpec(PRODUCT_SPEC, {}, 'application/json');
122+
spec = aBodySpec(
123+
PRODUCT_SPEC_WITH_DESCRIPTION,
124+
spec,
125+
'application/x-www-form-urlencoded',
126+
);
127+
class ProductControllerWithFullSchema {
128+
@post('/products')
129+
async create(
130+
@requestBody(spec) data: object,
131+
// ^^^^^^
132+
// use "object" instead of "Product" to verify the situation when
133+
// body schema cannot be inferred from the argument type
134+
): Promise<Product> {
135+
return new Product(data);
136+
}
137+
}
138+
139+
before(() => givenAnAppAndAClient(ProductControllerWithFullSchema));
140+
after(() => app.stop());
141+
142+
it('accepts valid values for json', () => serverAcceptsValidRequestBody());
143+
144+
it('accepts valid values for urlencoded', () =>
145+
serverAcceptsValidRequestBodyForUrlencoded());
146+
147+
it('rejects missing required properties for urlencoded', () =>
148+
serverRejectsMissingDescriptionForUrlencoded());
149+
});
150+
114151
// A request body schema can be provided explicitly by the user as a reference
115152
// to a schema shared in the global `components.schemas` object.
116153
context('for request body specified via a reference', () => {
@@ -156,6 +193,29 @@ describe('Validation at REST level', () => {
156193
.expect(200, DATA);
157194
}
158195

196+
async function serverAcceptsValidRequestBodyForUrlencoded() {
197+
const DATA =
198+
'name=Pencil&price=10&description=An optional description of a pencil';
199+
await client
200+
.post('/products')
201+
.set('Content-Type', 'application/x-www-form-urlencoded')
202+
.send(DATA)
203+
.expect(200, {
204+
name: 'Pencil',
205+
description: 'An optional description of a pencil',
206+
price: 10,
207+
});
208+
}
209+
210+
async function serverRejectsMissingDescriptionForUrlencoded() {
211+
const DATA = 'name=Pencil&price=10';
212+
await client
213+
.post('/products')
214+
.set('Content-Type', 'application/x-www-form-urlencoded')
215+
.send(DATA)
216+
.expect(422);
217+
}
218+
159219
async function serverRejectsRequestWithMissingRequiredValues() {
160220
await client
161221
.post('/products')

packages/rest/test/helpers.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,23 @@ export function createUnexpectedHttpErrorLogger(
3131
};
3232
}
3333

34+
/**
35+
* Create an OpenAPI request body spec with the given content
36+
* @param schema The schema object
37+
* @param options Other attributes for the spec
38+
* @param mediaType Optional media type, default to `application/json`
39+
*/
3440
export function aBodySpec(
3541
schema: SchemaObject | ReferenceObject,
36-
options?: Partial<RequestBodyObject>,
42+
options: Partial<RequestBodyObject> = {},
43+
mediaType: string = 'application/json',
3744
): RequestBodyObject {
38-
return Object.assign({}, options, {
39-
content: {
40-
'application/json': {
41-
schema: schema,
42-
},
45+
const spec = Object.assign({}, options);
46+
spec.content = spec.content || {};
47+
Object.assign(spec.content, {
48+
[mediaType]: {
49+
schema: schema,
4350
},
4451
});
52+
return spec as RequestBodyObject;
4553
}

0 commit comments

Comments
 (0)