@pactflow/swagger-mock-validator
Advanced tools
Comparing version 11.4.0 to 11.5.0
@@ -0,1 +1,11 @@ | ||
<a name="11.5.0"></a> | ||
# [11.5.0](https://github.com/pactflow/swagger-mock-validator/compare/11.4.0...11.5.0) (2023-05-12) | ||
### Features | ||
* handle more varieties of json ([e59bcdf](https://github.com/pactflow/swagger-mock-validator/commit/e59bcdf)) | ||
<a name="11.4.0"></a> | ||
@@ -2,0 +12,0 @@ # [11.4.0](https://github.com/pactflow/swagger-mock-validator/compare/11.3.1...11.4.0) (2023-05-05) |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getContentSchemasByContentType = exports.getDefaultContentSchema = void 0; | ||
exports.schemaByContentType = void 0; | ||
const get_schema_with_spec_definitions_1 = require("./get-schema-with-spec-definitions"); | ||
const content_negotiation_1 = require("../../../validate-spec-and-mock/content-negotiation"); | ||
const defaultMediaType = 'application/json'; | ||
@@ -10,28 +11,25 @@ const findDefaultMediaType = (content) => { | ||
}; | ||
const getApplicationJsonContentSchema = (content, spec) => { | ||
const mediaType = findDefaultMediaType(content); | ||
const schema = content[mediaType] ? content[mediaType].schema : undefined; | ||
return schema | ||
? { schema: (0, get_schema_with_spec_definitions_1.getSchemaWithSpecDefinitions)(schema, spec), mediaType } | ||
: { mediaType }; | ||
}; | ||
const getDefaultContentSchema = (content, spec) => content | ||
? getApplicationJsonContentSchema(content, spec) | ||
: { mediaType: defaultMediaType }; | ||
exports.getDefaultContentSchema = getDefaultContentSchema; | ||
// tslint:disable:cyclomatic-complexity | ||
const getContentSchemasByContentType = (content, spec) => { | ||
const result = {}; | ||
const schemaByContentType = (content, spec) => (mediaType) => { | ||
var _a; | ||
if (!content) { | ||
return result; | ||
return undefined; | ||
} | ||
const effectiveMediaType = mediaType || findDefaultMediaType(content); | ||
const normalizedMediaType = (0, content_negotiation_1.normalizeMediaType)(effectiveMediaType); | ||
const mediaTypes = Object.keys(content); | ||
for (const mediaType of mediaTypes) { | ||
if (content[mediaType] && content[mediaType].schema) { | ||
result[mediaType] = (0, get_schema_with_spec_definitions_1.getSchemaWithSpecDefinitions)(content[mediaType].schema, spec); | ||
} | ||
const contentMediaType = mediaTypes.find(type => (0, content_negotiation_1.areMediaTypesCompatible)((0, content_negotiation_1.normalizeMediaType)(type), normalizedMediaType)); | ||
if (!contentMediaType) { | ||
return undefined; | ||
} | ||
return result; | ||
const schema = (_a = content[contentMediaType]) === null || _a === void 0 ? void 0 : _a.schema; | ||
if (!schema) { | ||
return undefined; | ||
} | ||
return { | ||
schema: (0, get_schema_with_spec_definitions_1.getSchemaWithSpecDefinitions)(schema, spec), | ||
mediaType: contentMediaType | ||
}; | ||
}; | ||
exports.getContentSchemasByContentType = getContentSchemasByContentType; | ||
exports.schemaByContentType = schemaByContentType; | ||
// tslint:enable:cyclomatic-complexity |
@@ -9,19 +9,12 @@ "use strict"; | ||
const parseRequestBody = (parentOperation, requestBody, spec) => { | ||
const { schema, mediaType } = (0, get_content_schema_1.getDefaultContentSchema)(requestBody.content, spec); | ||
const schemasByContentType = (0, get_content_schema_1.getContentSchemasByContentType)(requestBody.content, spec); | ||
return schema | ||
? { | ||
getFromSchema: (pathToGet, actualMediaType) => { | ||
return { | ||
location: `${parentOperation.location}.requestBody.content.${actualMediaType || mediaType}.schema.${pathToGet}`, | ||
parentOperation, | ||
value: _.get(schema, pathToGet) | ||
}; | ||
}, | ||
name: '', | ||
required: requestBody.required || false, | ||
schema, | ||
schemasByContentType | ||
} | ||
: undefined; | ||
return { | ||
getFromSchema: (path, schema, mediaType) => ({ | ||
location: `${parentOperation.location}.requestBody.content.${mediaType}.schema.${path}`, | ||
parentOperation, | ||
value: _.get(schema, path) | ||
}), | ||
name: '', | ||
required: requestBody.required || false, | ||
schemaByContentType: (0, get_content_schema_1.schemaByContentType)(requestBody.content, spec) | ||
}; | ||
}; | ||
@@ -38,6 +31,5 @@ const createConsumesWithMimeTypes = (parentOperation, mimeTypes) => ({ | ||
const dereferencedRequestBody = (0, dereference_component_1.dereferenceComponent)(requestBody, spec); | ||
const requestBodyParameter = parseRequestBody(parentOperation, dereferencedRequestBody, spec); | ||
return { | ||
consumes: createConsumesWithMimeTypes(parentOperation, (0, get_content_mime_types_1.getContentMimeTypes)(dereferencedRequestBody.content)), | ||
requestBodyParameter | ||
requestBodyParameter: parseRequestBody(parentOperation, dereferencedRequestBody, spec) | ||
}; | ||
@@ -44,0 +36,0 @@ }; |
@@ -11,12 +11,8 @@ "use strict"; | ||
const dereferencedResponse = (0, dereference_component_1.dereferenceComponent)(response, spec); | ||
const { schema, mediaType } = (0, get_content_schema_1.getDefaultContentSchema)(dereferencedResponse.content, spec); | ||
const schemasByContentType = (0, get_content_schema_1.getContentSchemasByContentType)(dereferencedResponse.content, spec); | ||
return { | ||
getFromSchema: (pathToGet, actualMediaType) => { | ||
return { | ||
location: `${responseLocation}.content.${actualMediaType || mediaType}.schema.${pathToGet}`, | ||
parentOperation, | ||
value: _.get(schema, pathToGet) | ||
}; | ||
}, | ||
getFromSchema: (path, schema, mediaType) => ({ | ||
location: `${responseLocation}.content.${mediaType}.schema.${path}`, | ||
parentOperation, | ||
value: _.get(schema, path) | ||
}), | ||
headers: (0, parse_response_headers_1.parseResponseHeaders)(dereferencedResponse.headers, parentOperation, `${responseLocation}.headers`, spec), | ||
@@ -30,4 +26,3 @@ location: `${responseLocation}`, | ||
}, | ||
schema, | ||
schemasByContentType, | ||
schemaByContentType: (0, get_content_schema_1.schemaByContentType)(dereferencedResponse.content, spec), | ||
value: response | ||
@@ -34,0 +29,0 @@ }; |
@@ -13,8 +13,5 @@ "use strict"; | ||
const addDefinitionsToSchema = (schema, spec) => { | ||
if (schema) { | ||
const modifiedSchema = _.cloneDeep(schema); | ||
modifiedSchema.definitions = spec.definitions; | ||
return modifiedSchema; | ||
} | ||
return undefined; | ||
const modifiedSchema = _.cloneDeep(schema); | ||
modifiedSchema.definitions = spec.definitions; | ||
return modifiedSchema; | ||
}; | ||
@@ -91,8 +88,7 @@ const mergePathAndOperationParameters = (pathParameters, operationParameters) => { | ||
const responseLocation = `${parsedResponses.location}.${responseStatus}`; | ||
const originalSchema = response.schema; | ||
parsedResponses[responseStatus] = { | ||
getFromSchema: (pathToGet) => ({ | ||
location: `${responseLocation}.schema.${pathToGet}`, | ||
getFromSchema: (path, schema) => ({ | ||
location: `${responseLocation}.schema.${path}`, | ||
parentOperation, | ||
value: _.get(originalSchema, pathToGet) | ||
value: _.get(schema, path) | ||
}), | ||
@@ -103,3 +99,11 @@ headers: parseResponseHeaders(response.headers, responseLocation, parentOperation), | ||
produces, | ||
schema: addDefinitionsToSchema(response.schema, specJson), | ||
schemaByContentType: (mediaType) => { | ||
if (!response.schema) { | ||
return undefined; | ||
} | ||
return { | ||
schema: addDefinitionsToSchema(response.schema, specJson), | ||
mediaType | ||
}; | ||
}, | ||
value: response | ||
@@ -120,6 +124,6 @@ }; | ||
return { | ||
getFromSchema: (pathToGet) => ({ | ||
location: `${parameter.location}.schema.${pathToGet}`, | ||
getFromSchema: (path, schema, _mediaType) => ({ | ||
location: `${parameter.location}.schema.${path}`, | ||
parentOperation: parameter.parentOperation, | ||
value: _.get(parameter.value.schema, pathToGet) | ||
value: _.get(schema, path) | ||
}), | ||
@@ -130,3 +134,11 @@ location: parameter.location, | ||
required: parameter.value.required, | ||
schema: modifiedSchema, | ||
schemaByContentType: (mediaType) => { | ||
if (!parameter.value.schema) { | ||
return undefined; | ||
} | ||
return { | ||
schema: modifiedSchema, | ||
mediaType | ||
}; | ||
}, | ||
value: parameter.value | ||
@@ -133,0 +145,0 @@ }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isMediaTypeSupported = void 0; | ||
exports.isTypesOfJson = exports.isMediaTypeSupported = exports.normalizeMediaType = exports.areMediaTypesCompatible = void 0; | ||
const PARAMETER_SEPARATOR = ';'; | ||
@@ -23,2 +23,3 @@ const WILDCARD = '*'; | ||
}; | ||
exports.areMediaTypesCompatible = areMediaTypesCompatible; | ||
const normalizeMediaType = (mediaType) => { | ||
@@ -30,9 +31,17 @@ return mediaType | ||
}; | ||
exports.normalizeMediaType = normalizeMediaType; | ||
const isMediaTypeSupported = (actualMediaType, supportedMediaTypes) => { | ||
const normalizedActualMediaType = normalizeMediaType(actualMediaType); | ||
const normalizedActualMediaType = (0, exports.normalizeMediaType)(actualMediaType); | ||
return supportedMediaTypes.some((supportedMediaType) => { | ||
const normalizedSupportedMediaType = normalizeMediaType(supportedMediaType); | ||
return areMediaTypesCompatible(normalizedActualMediaType, normalizedSupportedMediaType); | ||
const normalizedSupportedMediaType = (0, exports.normalizeMediaType)(supportedMediaType); | ||
return (0, exports.areMediaTypesCompatible)(normalizedActualMediaType, normalizedSupportedMediaType); | ||
}); | ||
}; | ||
exports.isMediaTypeSupported = isMediaTypeSupported; | ||
const isTypesOfJson = (supportedMediaTypes) => { | ||
return supportedMediaTypes.some((supportedMediaType) => { | ||
const mediaType = (0, exports.normalizeMediaType)(supportedMediaType); | ||
return mediaType.startsWith('application/') && mediaType.endsWith('json') || mediaType === '*/*'; | ||
}); | ||
}; | ||
exports.isTypesOfJson = isTypesOfJson; |
@@ -12,10 +12,17 @@ "use strict"; | ||
const parsedSpecRequestBody = parsedSpecOperation.requestBodyParameter; | ||
// start with a default schema | ||
let schema = parsedSpecRequestBody.schema; | ||
// switch schema based on content-type | ||
const contentType = (_a = parsedMockInteraction.requestHeaders['content-type']) === null || _a === void 0 ? void 0 : _a.value; | ||
if (contentType && parsedSpecRequestBody.schemasByContentType && parsedSpecRequestBody.schemasByContentType[contentType]) { | ||
schema = parsedSpecRequestBody.schemasByContentType[contentType]; | ||
const expectedMediaType = (_a = parsedMockInteraction.requestHeaders['content-type']) === null || _a === void 0 ? void 0 : _a.value; | ||
const schemaForMediaType = parsedSpecRequestBody.schemaByContentType(expectedMediaType); | ||
if (!schemaForMediaType) { | ||
return [ | ||
result_1.result.build({ | ||
code: 'request.body.unknown', | ||
message: 'No matching schema found for request body', | ||
mockSegment: parsedMockInteraction.requestBody, | ||
source: 'spec-mock-validation', | ||
specSegment: parsedSpecOperation | ||
}) | ||
]; | ||
} | ||
const validationErrors = (0, validate_json_1.validateJson)(schema, parsedMockRequestBody.value); | ||
const { schema, mediaType } = schemaForMediaType; | ||
const validationErrors = (0, validate_json_1.validateJson)(schemaForMediaType.schema, parsedMockRequestBody.value); | ||
return _.map(validationErrors, (error) => result_1.result.build({ | ||
@@ -26,3 +33,3 @@ code: 'request.body.incompatible', | ||
source: 'spec-mock-validation', | ||
specSegment: parsedSpecRequestBody.getFromSchema(error.schemaPath.replace(/\//g, '.').substring(2), contentType) | ||
specSegment: parsedSpecRequestBody.getFromSchema(error.schemaPath.replace(/\//g, '.').substring(2), schema, mediaType) | ||
})); | ||
@@ -34,3 +41,3 @@ }; | ||
const isNotSupportedMediaType = (parsedSpecOperation) => parsedSpecOperation.consumes.value.length > 0 && | ||
!(0, content_negotiation_1.isMediaTypeSupported)('application/json', parsedSpecOperation.consumes.value); | ||
!(0, content_negotiation_1.isTypesOfJson)(parsedSpecOperation.consumes.value); | ||
const shouldSkipValidation = (parsedMockInteraction, parsedSpecOperation) => isNotSupportedMediaType(parsedSpecOperation) || | ||
@@ -41,34 +48,2 @@ specAndMockHaveNoBody(parsedMockInteraction, parsedSpecOperation) || | ||
if (shouldSkipValidation(parsedMockInteraction, parsedSpecOperation)) { | ||
// this is temporary code to identify passing validations that should've failed | ||
// tslint:disable:cyclomatic-complexity | ||
if (process.env.DEBUG_CONTENT_TYPE_ISSUE && isNotSupportedMediaType(parsedSpecOperation)) { | ||
const debugValidation = (validation) => { | ||
var _a; | ||
console.error(JSON.stringify({ | ||
message: 'Passing validation that should\'ve failed due to unsupported media type', | ||
pact_request: { | ||
'content-type': (_a = parsedMockInteraction.requestHeaders['content-type']) === null || _a === void 0 ? void 0 : _a.value | ||
}, | ||
oas_consumes: { | ||
'content-type': parsedSpecOperation.consumes.value | ||
}, | ||
validation | ||
})); | ||
}; | ||
if (parsedSpecOperation.requestBodyParameter) { | ||
debugValidation(validateRequestBodyAgainstSchema(parsedMockInteraction, parsedSpecOperation)); | ||
} | ||
else { | ||
debugValidation([ | ||
result_1.result.build({ | ||
code: 'request.body.unknown', | ||
message: 'No schema found for request body', | ||
mockSegment: parsedMockInteraction.requestBody, | ||
source: 'spec-mock-validation', | ||
specSegment: parsedSpecOperation | ||
}) | ||
]); | ||
} | ||
} | ||
// tslint:enable:cyclomatic-complexity | ||
return []; | ||
@@ -75,0 +50,0 @@ } |
@@ -29,5 +29,6 @@ "use strict"; | ||
const isNotSupportedMediaType = (parsedSpecResponse) => parsedSpecResponse.produces.value.length > 0 && | ||
!(0, content_negotiation_1.isMediaTypeSupported)('application/json', parsedSpecResponse.produces.value); | ||
!(0, content_negotiation_1.isTypesOfJson)(parsedSpecResponse.produces.value); | ||
const shouldSkipValidation = (parsedMockInteraction, parsedSpecResponse) => isMockInteractionWithoutResponseBody(parsedMockInteraction) || | ||
isNotSupportedMediaType(parsedSpecResponse); | ||
// tslint:disable:cyclomatic-complexity | ||
const validateParsedMockResponseBody = (parsedMockInteraction, parsedSpecResponse, opts) => { | ||
@@ -38,3 +39,5 @@ var _a; | ||
} | ||
if (!parsedSpecResponse.schema) { | ||
const expectedMediaType = (_a = parsedMockInteraction.requestHeaders.accept) === null || _a === void 0 ? void 0 : _a.value; | ||
const schemaForMediaType = parsedSpecResponse.schemaByContentType(expectedMediaType); | ||
if (!schemaForMediaType) { | ||
return [ | ||
@@ -50,17 +53,11 @@ result_1.result.build({ | ||
} | ||
// start with a default schema | ||
let responseBodyToValidate = parsedSpecResponse.schema; | ||
// switch schema based on content-type | ||
const contentType = (_a = parsedMockInteraction.responseHeaders['content-type']) === null || _a === void 0 ? void 0 : _a.value; | ||
if (contentType && parsedSpecResponse.schemasByContentType && parsedSpecResponse.schemasByContentType[contentType]) { | ||
responseBodyToValidate = parsedSpecResponse.schemasByContentType[contentType]; | ||
} | ||
// tslint:disable:cyclomatic-complexity | ||
let schema = schemaForMediaType.schema; | ||
const mediaType = schemaForMediaType.mediaType; | ||
if (!opts.additionalPropertiesInResponse) { | ||
responseBodyToValidate = setAdditionalPropertiesToFalseInSchema(responseBodyToValidate); | ||
schema = setAdditionalPropertiesToFalseInSchema(schema); | ||
} | ||
if (!opts.requiredPropertiesInResponse) { | ||
responseBodyToValidate = removeRequiredPropertiesFromSchema(responseBodyToValidate); | ||
schema = removeRequiredPropertiesFromSchema(schema); | ||
} | ||
const validationErrors = (0, validate_json_1.validateJson)(responseBodyToValidate, parsedMockInteraction.responseBody.value); | ||
const validationErrors = (0, validate_json_1.validateJson)(schema, parsedMockInteraction.responseBody.value); | ||
return _.map(validationErrors, (error) => { | ||
@@ -75,3 +72,3 @@ const message = error.keyword === 'additionalProperties' | ||
source: 'spec-mock-validation', | ||
specSegment: parsedSpecResponse.getFromSchema(error.schemaPath.replace(/\//g, '.').substring(2), contentType) | ||
specSegment: parsedSpecResponse.getFromSchema(error.schemaPath.replace(/\//g, '.').substring(2), schema, mediaType) | ||
}); | ||
@@ -81,1 +78,2 @@ }); | ||
exports.validateParsedMockResponseBody = validateParsedMockResponseBody; | ||
// tslint:enable:cyclomatic-complexity |
{ | ||
"name": "@pactflow/swagger-mock-validator", | ||
"version": "11.4.0", | ||
"version": "11.5.0", | ||
"description": "A CLI tool to validate mocks against swagger/OpenApi specs.", | ||
@@ -5,0 +5,0 @@ "bin": { |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
1
215199
2955