Skip to content

Commit dd98515

Browse files
authored
fix: only parse query/headers as necessary (#46)
1 parent 030b9cc commit dd98515

File tree

9 files changed

+157
-12
lines changed

9 files changed

+157
-12
lines changed

src/__tests__/fixtures/request-parameters-header/baseline.json

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"items": {
4040
"type": "integer"
4141
},
42-
"minItems": 1
42+
"minItems": 1,
43+
"maxItems": 3
4344
}
4445
},
4546
{
@@ -57,6 +58,15 @@
5758
}
5859
}
5960
}
61+
},
62+
{
63+
"in": "header",
64+
"name": "x-optional-string",
65+
"required": false,
66+
"schema": {
67+
"type": "string",
68+
"minLength": 20
69+
}
6070
}
6171
],
6272
"responses": {
@@ -228,5 +238,30 @@
228238
]
229239
},
230240
"type": "error"
241+
},
242+
{
243+
"code": "request.header.incompatible",
244+
"message": "Value is incompatible with the parameter defined in the spec file: must NOT have fewer than 20 characters",
245+
"mockDetails": {
246+
"interactionDescription": "should parse arrays, but not strings",
247+
"interactionState": "[none]",
248+
"location": "[root].interactions[11].request.headers.x-optional-string",
249+
"value": "abc,def,xyz"
250+
},
251+
"specDetails": {
252+
"location": "[root].paths./path.get.parameters[4]",
253+
"pathMethod": "get",
254+
"pathName": "/path",
255+
"value": {
256+
"in": "header",
257+
"name": "x-optional-string",
258+
"required": false,
259+
"schema": {
260+
"type": "string",
261+
"minLength": 20
262+
}
263+
}
264+
},
265+
"type": "error"
231266
}
232267
]

src/__tests__/fixtures/request-parameters-header/oas.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ paths:
2424
items:
2525
type: integer
2626
minItems: 1
27+
maxItems: 3
2728
- in: header
2829
name: x-optional-object
2930
required: false
@@ -34,6 +35,12 @@ paths:
3435
type: string
3536
number:
3637
type: number
38+
- in: header
39+
name: x-optional-string
40+
required: false
41+
schema:
42+
type: string
43+
minLength: 20
3744
responses:
3845
"200":
3946
description: OK

src/__tests__/fixtures/request-parameters-header/pact.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,24 @@
211211
"Content-Type": "application/json"
212212
}
213213
}
214+
},
215+
{
216+
"description": "should parse arrays, but not strings",
217+
"request": {
218+
"method": "GET",
219+
"path": "/path",
220+
"headers": {
221+
"x-required-number": "123",
222+
"x-optional-array": "123,456,789,0",
223+
"x-optional-string": "abc,def,xyz"
224+
}
225+
},
226+
"response": {
227+
"status": 200,
228+
"headers": {
229+
"Content-Type": "application/json"
230+
}
231+
}
214232
}
215233
]
216234
}

src/__tests__/fixtures/request-parameters-header/results.json

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"items": {
4040
"type": "integer"
4141
},
42-
"minItems": 1
42+
"minItems": 1,
43+
"maxItems": 3
4344
}
4445
},
4546
{
@@ -57,6 +58,15 @@
5758
}
5859
}
5960
}
61+
},
62+
{
63+
"in": "header",
64+
"name": "x-optional-string",
65+
"required": false,
66+
"schema": {
67+
"type": "string",
68+
"minLength": 20
69+
}
6070
}
6171
],
6272
"responses": {
@@ -218,5 +228,44 @@
218228
]
219229
},
220230
"type": "error"
231+
},
232+
{
233+
"code": "request.header.incompatible",
234+
"message": "Value is incompatible with the parameter defined in the spec file: must NOT have more than 3 items",
235+
"mockDetails": {
236+
"interactionDescription": "should parse arrays, but not strings",
237+
"interactionState": "[none]",
238+
"location": "[root].interactions[11].request.headers.x-optional-array",
239+
"value": [
240+
123,
241+
456,
242+
789,
243+
0
244+
]
245+
},
246+
"specDetails": {
247+
"location": "[root].paths./path.get.parameters[2].schema.maxItems",
248+
"pathMethod": "get",
249+
"pathName": "/path",
250+
"value": 3
251+
},
252+
"type": "error"
253+
},
254+
{
255+
"code": "request.header.incompatible",
256+
"message": "Value is incompatible with the parameter defined in the spec file: must NOT have fewer than 20 characters",
257+
"mockDetails": {
258+
"interactionDescription": "should parse arrays, but not strings",
259+
"interactionState": "[none]",
260+
"location": "[root].interactions[11].request.headers.x-optional-string",
261+
"value": "abc,def,xyz"
262+
},
263+
"specDetails": {
264+
"location": "[root].paths./path.get.parameters[4].schema.minLength",
265+
"pathMethod": "get",
266+
"pathName": "/path",
267+
"value": 20
268+
},
269+
"type": "error"
221270
}
222271
]

src/__tests__/fixtures/request-parameters-query/oas.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ paths:
110110
type: array
111111
items:
112112
type: string
113+
- in: query
114+
name: string
115+
schema:
116+
type: string
117+
minLength: 20
113118
responses:
114119
"200":
115120
description: OK

src/__tests__/fixtures/request-parameters-query/pact.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,13 @@
180180
}
181181
},
182182
{
183-
"description": "should error when missing required query parameter",
183+
"description": "should parse arrays, but not with strings",
184184
"request": {
185185
"method": "GET",
186186
"path": "/arrays",
187187
"query": {
188-
"things": ["first"]
188+
"things": ["first", "second"],
189+
"string": ["first", "second"]
189190
}
190191
},
191192
"response": {

src/__tests__/fixtures/request-parameters-query/results.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,5 +188,22 @@
188188
"value": "string"
189189
},
190190
"type": "error"
191+
},
192+
{
193+
"code": "request.query.incompatible",
194+
"message": "Value is incompatible with the parameter defined in the spec file: must NOT have fewer than 20 characters",
195+
"mockDetails": {
196+
"interactionDescription": "should parse arrays, but not with strings",
197+
"interactionState": "[none]",
198+
"location": "[root].interactions[15].request.query.string",
199+
"value": "first,second"
200+
},
201+
"specDetails": {
202+
"location": "[root].paths./arrays.get.parameters[1].schema.minLength",
203+
"pathMethod": "get",
204+
"pathName": "/arrays",
205+
"value": 20
206+
},
207+
"type": "error"
191208
}
192209
]

src/compare/requestHeader.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,11 @@ export function* compareReqHeader(
288288
const schema: SchemaObject = dereferencedParameter.schema || {
289289
type: dereferencedParameter.type,
290290
};
291-
const value = parseValue(requestHeaders.get(dereferencedParameter.name));
291+
292+
const value =
293+
dereferencedParameter.schema?.type === "string"
294+
? requestHeaders.get(dereferencedParameter.name)
295+
: parseValue(requestHeaders.get(dereferencedParameter.name));
292296

293297
if (value && schema && isValidRequest(interaction)) {
294298
const schemaId = `[root].paths.${path}.${method}.parameters[${parameterIndex}]`;

src/compare/requestQuery.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ export function* compareReqQuery(
2626
): Iterable<Result> {
2727
const { method, oas, operation, path, securitySchemes } = route.store;
2828

29-
// TODO: parse different parameters differently?
30-
const searchParams = qs.parse(route.searchParams, {
29+
const searchParamsParsed = qs.parse(route.searchParams, {
3130
allowDots: true,
3231
comma: true,
3332
});
33+
const searchParamsUnparsed = qs.parse(route.searchParams, {
34+
allowDots: false,
35+
comma: false,
36+
});
3437

3538
for (const [parameterIndex, parameter] of (
3639
operation.parameters || []
@@ -42,7 +45,13 @@ export function* compareReqQuery(
4245
const schema: SchemaObject = dereferencedParameter.schema || {
4346
type: dereferencedParameter.type,
4447
};
45-
const value = searchParams[dereferencedParameter.name];
48+
49+
if (dereferencedParameter.schema?.type === "string") {
50+
searchParamsParsed[dereferencedParameter.name] =
51+
searchParamsUnparsed[dereferencedParameter.name];
52+
}
53+
54+
const value = searchParamsParsed[dereferencedParameter.name];
4655
if (
4756
schema &&
4857
(value || dereferencedParameter.required) &&
@@ -82,7 +91,7 @@ export function* compareReqQuery(
8291
}
8392
}
8493
}
85-
delete searchParams[dereferencedParameter.name];
94+
delete searchParamsParsed[dereferencedParameter.name];
8695
}
8796

8897
if (isValidRequest(interaction)) {
@@ -93,7 +102,7 @@ export function* compareReqQuery(
93102
case "apiKey":
94103
switch (scheme.in) {
95104
case "query":
96-
if (!searchParams[scheme.name]) {
105+
if (!searchParamsParsed[scheme.name]) {
97106
yield {
98107
code: "request.authorization.missing",
99108
message:
@@ -112,7 +121,7 @@ export function* compareReqQuery(
112121
type: "error",
113122
};
114123
}
115-
delete searchParams[scheme.name];
124+
delete searchParamsParsed[scheme.name];
116125
break;
117126
case "cookie":
118127
case "header":
@@ -136,7 +145,7 @@ export function* compareReqQuery(
136145
}
137146

138147
if (isValidRequest(interaction)) {
139-
for (const [key, value] of Object.entries(searchParams)) {
148+
for (const [key, value] of Object.entries(searchParamsParsed)) {
140149
yield {
141150
code: "request.query.unknown",
142151
message: `Query parameter is not defined in the spec file: ${key}`,

0 commit comments

Comments
 (0)