Skip to content

Commit 1beb454

Browse files
authored
Merge pull request #488 from postmanlabs/fix478/invalidValueInPathVariablesValidation
Fix #478: invalid value in path variables validation
2 parents c4c5ea3 + 2ebd3ad commit 1beb454

14 files changed

+1747
-12
lines changed

lib/common/schemaUtilsCommon.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,5 +345,16 @@ module.exports = {
345345

346346
isKnownType: function(schema) {
347347
return typeof schemaTypeToJsValidator[schema.type] === 'function';
348+
},
349+
350+
getServersPathVars: function(servers) {
351+
return servers.reduce((acc, current) => {
352+
const newVarNames = current.hasOwnProperty('variables') ?
353+
Object.keys(current.variables).filter((varName) => {
354+
return !acc.includes(varName);
355+
}) :
356+
[];
357+
return [...acc, ...newVarNames];
358+
}, []);
348359
}
349360
};

lib/schemaUtils.js

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3322,7 +3322,7 @@ module.exports = {
33223322

33233323
/**
33243324
*
3325-
* @param {*} determinedPathVariables the key/determined-value pairs of the path variables (from Postman)
3325+
* @param {*} matchedPathData the matchedPath data
33263326
* @param {*} transactionPathPrefix the jsonpath for this validation (will be prepended to all identified mismatches)
33273327
* @param {*} schemaPath the applicable pathItem defined at the schema level
33283328
* @param {*} components the components + paths from the OAS spec that need to be used to resolve $refs
@@ -3333,7 +3333,7 @@ module.exports = {
33333333
* @returns {array} mismatches (in the callback)
33343334
*/
33353335
checkPathVariables: function (
3336-
determinedPathVariables,
3336+
matchedPathData,
33373337
transactionPathPrefix,
33383338
schemaPath,
33393339
components,
@@ -3347,7 +3347,9 @@ module.exports = {
33473347
var mismatchProperty = 'PATHVARIABLE',
33483348
// all path variables defined in this path. acc. to the spec, all path params are required
33493349
schemaPathVariables,
3350-
pmPathVariables;
3350+
pmPathVariables,
3351+
determinedPathVariables = matchedPathData.pathVariables,
3352+
unmatchedVariablesFromTransaction = matchedPathData.unmatchedVariablesFromTransaction;
33513353

33523354
if (options.validationPropertiesToIgnore.includes(mismatchProperty)) {
33533355
return callback(null, []);
@@ -3413,17 +3415,41 @@ module.exports = {
34133415
}, (err, res) => {
34143416
let mismatches = [],
34153417
mismatchObj;
3418+
const unmatchedSchemaVariableNames = determinedPathVariables.filter((pathVariable) => {
3419+
return !pathVariable._varMatched;
3420+
}).map((schemaPathVar) => {
3421+
return schemaPathVar.key;
3422+
});
34163423

34173424
if (err) {
34183425
return callback(err);
34193426
}
34203427

34213428
// go through required schemaPathVariables, and params that aren't found in the given transaction are errors
3422-
_.each(schemaPathVariables, (pathVar) => {
3429+
_.each(schemaPathVariables, (pathVar, index) => {
34233430
if (!_.find(determinedPathVariables, (param) => {
34243431
// only consider variable matching if url path variables is not allowed
34253432
return param.key === pathVar.name && (options.allowUrlPathVarMatching || param._varMatched);
34263433
})) {
3434+
let reasonCode = 'MISSING_IN_REQUEST',
3435+
reason,
3436+
actualValue,
3437+
currentUnmatchedVariableInTransaction = unmatchedVariablesFromTransaction[index],
3438+
isInvalidValue = currentUnmatchedVariableInTransaction !== undefined;
3439+
3440+
if (unmatchedSchemaVariableNames.length > 0 && isInvalidValue) {
3441+
reason = `The ${currentUnmatchedVariableInTransaction.key} path variable does not match with ` +
3442+
`path variable expected (${unmatchedSchemaVariableNames[index]}) in the schema at this position`;
3443+
actualValue = {
3444+
key: currentUnmatchedVariableInTransaction.key,
3445+
description: this.getParameterDescription(currentUnmatchedVariableInTransaction),
3446+
value: currentUnmatchedVariableInTransaction.value
3447+
};
3448+
}
3449+
else {
3450+
reason = `The required path variable "${pathVar.name}" was not found in the transaction`;
3451+
actualValue = null;
3452+
}
34273453

34283454
// assign parameter example(s) as schema examples;
34293455
this.assignParameterExamples(pathVar);
@@ -3432,14 +3458,14 @@ module.exports = {
34323458
property: mismatchProperty,
34333459
transactionJsonPath: transactionPathPrefix,
34343460
schemaJsonPath: pathVar.pathPrefix,
3435-
reasonCode: 'MISSING_IN_REQUEST',
3436-
reason: `The required path variable "${pathVar.name}" was not found in the transaction`
3461+
reasonCode,
3462+
reason
34373463
};
34383464

34393465
if (options.suggestAvailableFixes) {
34403466
mismatchObj.suggestedFix = {
34413467
key: pathVar.name,
3442-
actualValue: null,
3468+
actualValue,
34433469
suggestedValue: {
34443470
key: pathVar.name,
34453471
value: safeSchemaFaker(pathVar.schema || {}, 'example', PROCESSING_TYPE.VALIDATION,

lib/schemapack.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict';
22

3-
43
// This is the default collection name if one can't be inferred from the OpenAPI spec
54
const COLLECTION_NAME = 'Imported from OpenAPI 3.0',
65
{ getConcreteSchemaUtils } = require('./common/versionUtils.js'),
@@ -26,7 +25,8 @@ const COLLECTION_NAME = 'Imported from OpenAPI 3.0',
2625
// This provides the base class for
2726
// errors with the input OpenAPI spec
2827
OpenApiErr = require('./error.js'),
29-
schemaUtils = require('./schemaUtils');
28+
schemaUtils = require('./schemaUtils'),
29+
{ getServersPathVars } = require('./common/schemaUtilsCommon.js');
3030

3131
let path = require('path'),
3232
concreteUtils,
@@ -495,12 +495,29 @@ class SchemaPack {
495495
return setTimeout(() => {
496496
// 2. perform validation for each identified matchedPath (schema endpoint)
497497
return async.map(matchedPaths, (matchedPath, pathsCallback) => {
498+
const transactionPathVariables = _.get(transaction, 'request.url.variable', []),
499+
localServers = matchedPath.path.hasOwnProperty('servers') ?
500+
matchedPath.path.servers :
501+
[],
502+
serversPathVars = [...getServersPathVars(localServers), ...getServersPathVars(schema.servers)],
503+
isNotAServerPathVar = (pathVarName) => {
504+
return !serversPathVars.includes(pathVarName);
505+
};
498506

507+
matchedPath.unmatchedVariablesFromTransaction = [];
499508
// override path variable value with actual value present in transaction
500509
// as matched pathvariable contains key as value, as it is generated from url only
501510
_.forEach(matchedPath.pathVariables, (pathVar) => {
502-
let mappedPathVar = _.find(_.get(transaction, 'request.url.variable', []), (transactionPathVar) => {
503-
return transactionPathVar.key === pathVar.key;
511+
const mappedPathVar = _.find(transactionPathVariables, (transactionPathVar) => {
512+
let matched = transactionPathVar.key === pathVar.key;
513+
if (
514+
!matched &&
515+
isNotAServerPathVar(transactionPathVar.key) &&
516+
!matchedPath.unmatchedVariablesFromTransaction.includes(transactionPathVar)
517+
) {
518+
matchedPath.unmatchedVariablesFromTransaction.push(transactionPathVar);
519+
}
520+
return matched;
504521
});
505522
pathVar.value = _.get(mappedPathVar, 'value', pathVar.value);
506523
// set _varMatched flag which represents if variable was found in transaction or not
@@ -522,7 +539,7 @@ class SchemaPack {
522539
schemaUtils.checkMetadata(transaction, '$', matchedPath.path, matchedPath.name, options, cb);
523540
},
524541
path: function(cb) {
525-
schemaUtils.checkPathVariables(matchedPath.pathVariables, '$.request.url.variable', matchedPath.path,
542+
schemaUtils.checkPathVariables(matchedPath, '$.request.url.variable', matchedPath.path,
526543
componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
527544
},
528545
queryparams: function(cb) {
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
{
2+
"item": [
3+
{
4+
"id": "1c9e80af-cc42-47c7-beae-f4b78e1bd3e1",
5+
"name": "Info for a specific pet",
6+
"request": {
7+
"name": "Info for a specific pet",
8+
"description": {},
9+
"url": {
10+
"path": [
11+
"pets",
12+
":petId"
13+
],
14+
"host": [
15+
"{{baseUrl}}"
16+
],
17+
"query": [],
18+
"variable": [
19+
{
20+
"description": "Assigned by the service provider",
21+
"type": "any",
22+
"value": "{{username}}",
23+
"key": "username"
24+
},
25+
{
26+
"description": " (This can only be one of 8843,443)",
27+
"type": "any",
28+
"value": "{{port}}",
29+
"key": "port"
30+
},
31+
{
32+
"description": {
33+
"content": "",
34+
"type": "text/plain"
35+
},
36+
"type": "any",
37+
"value": "{{basePath}}",
38+
"key": "basePath"
39+
},
40+
{
41+
"disabled": false,
42+
"type": "any",
43+
"value": "<string>",
44+
"key": "petId",
45+
"description": "(Required) The id of the pet to retrieve"
46+
}
47+
]
48+
},
49+
"header": [
50+
{
51+
"key": "Accept",
52+
"value": "application/json"
53+
}
54+
],
55+
"method": "GET",
56+
"auth": null
57+
},
58+
"response": [
59+
{
60+
"id": "291f119a-d162-4676-83c0-91ce6595407b",
61+
"name": "Expected response to a valid request",
62+
"originalRequest": {
63+
"url": {
64+
"path": [
65+
"pets",
66+
":petId"
67+
],
68+
"host": [
69+
"{{baseUrl}}"
70+
],
71+
"query": [],
72+
"variable": [
73+
{
74+
"description": "Assigned by the service provider",
75+
"type": "any",
76+
"value": "{{username}}",
77+
"key": "username"
78+
},
79+
{
80+
"description": " (This can only be one of 8843,443)",
81+
"type": "any",
82+
"value": "{{port}}",
83+
"key": "port"
84+
},
85+
{
86+
"description": {
87+
"content": "",
88+
"type": "text/plain"
89+
},
90+
"type": "any",
91+
"value": "{{basePath}}",
92+
"key": "basePath"
93+
},
94+
{
95+
"disabled": false,
96+
"type": "any",
97+
"value": "<string>",
98+
"key": "petId",
99+
"description": "(Required) The id of the pet to retrieve"
100+
}
101+
]
102+
},
103+
"method": "GET",
104+
"body": {}
105+
},
106+
"status": "OK",
107+
"code": 200,
108+
"header": [
109+
{
110+
"key": "Content-Type",
111+
"value": "application/json"
112+
}
113+
],
114+
"body": "{\n \"id\": -74238905,\n \"name\": \"et aliqua officia\",\n \"tag\": \"qui do\"\n}",
115+
"cookie": [],
116+
"_postman_previewlanguage": "json"
117+
},
118+
{
119+
"id": "c00295c6-c859-46a4-8aae-8e2a6a2f64f3",
120+
"name": "unexpected error",
121+
"originalRequest": {
122+
"url": {
123+
"path": [
124+
"pets",
125+
":petId"
126+
],
127+
"host": [
128+
"{{baseUrl}}"
129+
],
130+
"query": [],
131+
"variable": [
132+
{
133+
"description": "Assigned by the service provider",
134+
"type": "any",
135+
"value": "{{username}}",
136+
"key": "username"
137+
},
138+
{
139+
"description": " (This can only be one of 8843,443)",
140+
"type": "any",
141+
"value": "{{port}}",
142+
"key": "port"
143+
},
144+
{
145+
"description": {
146+
"content": "",
147+
"type": "text/plain"
148+
},
149+
"type": "any",
150+
"value": "{{basePath}}",
151+
"key": "basePath"
152+
},
153+
{
154+
"disabled": false,
155+
"type": "any",
156+
"value": "<string>",
157+
"key": "petId",
158+
"description": "(Required) The id of the pet to retrieve"
159+
}
160+
]
161+
},
162+
"method": "GET",
163+
"body": {}
164+
},
165+
"status": "Internal Server Error",
166+
"code": 500,
167+
"header": [
168+
{
169+
"key": "Content-Type",
170+
"value": "application/json"
171+
}
172+
],
173+
"body": "{\n \"code\": -79318973,\n \"message\": \"ex c\"\n}",
174+
"cookie": [],
175+
"_postman_previewlanguage": "json"
176+
}
177+
],
178+
"event": []
179+
}
180+
],
181+
"event": [],
182+
"variable": [
183+
{
184+
"description": {
185+
"content": "Assigned by the service provider",
186+
"type": "text/plain"
187+
},
188+
"type": "any",
189+
"value": "demo",
190+
"key": "username"
191+
},
192+
{
193+
"description": {
194+
"content": " (This can only be one of 8843,443)",
195+
"type": "text/plain"
196+
},
197+
"type": "any",
198+
"value": "8843",
199+
"key": "port"
200+
},
201+
{
202+
"description": {
203+
"content": "",
204+
"type": "text/plain"
205+
},
206+
"type": "any",
207+
"value": "v2",
208+
"key": "basePath"
209+
},
210+
{
211+
"type": "string",
212+
"value": "https://{{username}}.myTestServer.com:{{port}}/{{basePath}}",
213+
"key": "baseUrl"
214+
}
215+
],
216+
"info": {
217+
"_postman_id": "a1d9a3d3-6195-46b3-95ff-016edd9b283d",
218+
"name": "Swagger Petstore",
219+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
220+
"description": {
221+
"content": "",
222+
"type": "text/plain"
223+
}
224+
}
225+
}

0 commit comments

Comments
 (0)