Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion src/__tests__/fixtures/request-parameters-header/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"items": {
"type": "integer"
},
"minItems": 1
"minItems": 1,
"maxItems": 3
}
},
{
Expand All @@ -57,6 +58,15 @@
}
}
}
},
{
"in": "header",
"name": "x-optional-string",
"required": false,
"schema": {
"type": "string",
"minLength": 20
}
}
],
"responses": {
Expand Down Expand Up @@ -228,5 +238,30 @@
]
},
"type": "error"
},
{
"code": "request.header.incompatible",
"message": "Value is incompatible with the parameter defined in the spec file: must NOT have fewer than 20 characters",
"mockDetails": {
"interactionDescription": "should parse arrays, but not strings",
"interactionState": "[none]",
"location": "[root].interactions[11].request.headers.x-optional-string",
"value": "abc,def,xyz"
},
"specDetails": {
"location": "[root].paths./path.get.parameters[4]",
"pathMethod": "get",
"pathName": "/path",
"value": {
"in": "header",
"name": "x-optional-string",
"required": false,
"schema": {
"type": "string",
"minLength": 20
}
}
},
"type": "error"
}
]
7 changes: 7 additions & 0 deletions src/__tests__/fixtures/request-parameters-header/oas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ paths:
items:
type: integer
minItems: 1
maxItems: 3
- in: header
name: x-optional-object
required: false
Expand All @@ -34,6 +35,12 @@ paths:
type: string
number:
type: number
- in: header
name: x-optional-string
required: false
schema:
type: string
minLength: 20
responses:
"200":
description: OK
Expand Down
18 changes: 18 additions & 0 deletions src/__tests__/fixtures/request-parameters-header/pact.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,24 @@
"Content-Type": "application/json"
}
}
},
{
"description": "should parse arrays, but not strings",
"request": {
"method": "GET",
"path": "/path",
"headers": {
"x-required-number": "123",
"x-optional-array": "123,456,789,0",
"x-optional-string": "abc,def,xyz"
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
}
}
}
]
}
51 changes: 50 additions & 1 deletion src/__tests__/fixtures/request-parameters-header/results.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"items": {
"type": "integer"
},
"minItems": 1
"minItems": 1,
"maxItems": 3
}
},
{
Expand All @@ -57,6 +58,15 @@
}
}
}
},
{
"in": "header",
"name": "x-optional-string",
"required": false,
"schema": {
"type": "string",
"minLength": 20
}
}
],
"responses": {
Expand Down Expand Up @@ -218,5 +228,44 @@
]
},
"type": "error"
},
{
"code": "request.header.incompatible",
"message": "Value is incompatible with the parameter defined in the spec file: must NOT have more than 3 items",
"mockDetails": {
"interactionDescription": "should parse arrays, but not strings",
"interactionState": "[none]",
"location": "[root].interactions[11].request.headers.x-optional-array",
"value": [
123,
456,
789,
0
]
},
"specDetails": {
"location": "[root].paths./path.get.parameters[2].schema.maxItems",
"pathMethod": "get",
"pathName": "/path",
"value": 3
},
"type": "error"
},
{
"code": "request.header.incompatible",
"message": "Value is incompatible with the parameter defined in the spec file: must NOT have fewer than 20 characters",
"mockDetails": {
"interactionDescription": "should parse arrays, but not strings",
"interactionState": "[none]",
"location": "[root].interactions[11].request.headers.x-optional-string",
"value": "abc,def,xyz"
},
"specDetails": {
"location": "[root].paths./path.get.parameters[4].schema.minLength",
"pathMethod": "get",
"pathName": "/path",
"value": 20
},
"type": "error"
}
]
5 changes: 5 additions & 0 deletions src/__tests__/fixtures/request-parameters-query/oas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ paths:
type: array
items:
type: string
- in: query
name: string
schema:
type: string
minLength: 20
responses:
"200":
description: OK
Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/fixtures/request-parameters-query/pact.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,13 @@
}
},
{
"description": "should error when missing required query parameter",
"description": "should parse arrays, but not with strings",
"request": {
"method": "GET",
"path": "/arrays",
"query": {
"things": ["first"]
"things": ["first", "second"],
"string": ["first", "second"]
}
},
"response": {
Expand Down
17 changes: 17 additions & 0 deletions src/__tests__/fixtures/request-parameters-query/results.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,22 @@
"value": "string"
},
"type": "error"
},
{
"code": "request.query.incompatible",
"message": "Value is incompatible with the parameter defined in the spec file: must NOT have fewer than 20 characters",
"mockDetails": {
"interactionDescription": "should parse arrays, but not with strings",
"interactionState": "[none]",
"location": "[root].interactions[15].request.query.string",
"value": "first,second"
},
"specDetails": {
"location": "[root].paths./arrays.get.parameters[1].schema.minLength",
"pathMethod": "get",
"pathName": "/arrays",
"value": 20
},
"type": "error"
}
]
6 changes: 5 additions & 1 deletion src/compare/requestHeader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,11 @@ export function* compareReqHeader(
const schema: SchemaObject = dereferencedParameter.schema || {
type: dereferencedParameter.type,
};
const value = parseValue(requestHeaders.get(dereferencedParameter.name));

const value =
dereferencedParameter.schema?.type === "string"
? requestHeaders.get(dereferencedParameter.name)
: parseValue(requestHeaders.get(dereferencedParameter.name));

if (value && schema && isValidRequest(interaction)) {
const schemaId = `[root].paths.${path}.${method}.parameters[${parameterIndex}]`;
Expand Down
23 changes: 16 additions & 7 deletions src/compare/requestQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ export function* compareReqQuery(
): Iterable<Result> {
const { method, oas, operation, path, securitySchemes } = route.store;

// TODO: parse different parameters differently?
const searchParams = qs.parse(route.searchParams, {
const searchParamsParsed = qs.parse(route.searchParams, {
allowDots: true,
comma: true,
});
const searchParamsUnparsed = qs.parse(route.searchParams, {
allowDots: false,
comma: false,
});

for (const [parameterIndex, parameter] of (
operation.parameters || []
Expand All @@ -42,7 +45,13 @@ export function* compareReqQuery(
const schema: SchemaObject = dereferencedParameter.schema || {
type: dereferencedParameter.type,
};
const value = searchParams[dereferencedParameter.name];

if (dereferencedParameter.schema?.type === "string") {
searchParamsParsed[dereferencedParameter.name] =
searchParamsUnparsed[dereferencedParameter.name];
}

const value = searchParamsParsed[dereferencedParameter.name];
if (
schema &&
(value || dereferencedParameter.required) &&
Expand Down Expand Up @@ -82,7 +91,7 @@ export function* compareReqQuery(
}
}
}
delete searchParams[dereferencedParameter.name];
delete searchParamsParsed[dereferencedParameter.name];
}

if (isValidRequest(interaction)) {
Expand All @@ -93,7 +102,7 @@ export function* compareReqQuery(
case "apiKey":
switch (scheme.in) {
case "query":
if (!searchParams[scheme.name]) {
if (!searchParamsParsed[scheme.name]) {
yield {
code: "request.authorization.missing",
message:
Expand All @@ -112,7 +121,7 @@ export function* compareReqQuery(
type: "error",
};
}
delete searchParams[scheme.name];
delete searchParamsParsed[scheme.name];
break;
case "cookie":
case "header":
Expand All @@ -136,7 +145,7 @@ export function* compareReqQuery(
}

if (isValidRequest(interaction)) {
for (const [key, value] of Object.entries(searchParams)) {
for (const [key, value] of Object.entries(searchParamsParsed)) {
yield {
code: "request.query.unknown",
message: `Query parameter is not defined in the spec file: ${key}`,
Expand Down