@redocly/respect-core
Version:
API testing framework core
116 lines • 5.26 kB
JavaScript
import Ajv from '@redocly/ajv/dist/2020.js';
import { diffLinesUnified } from 'jest-diff';
import { blue, dim, red, yellow } from 'colorette';
import { CHECKS } from '../../checks/index.js';
import { printErrors as printAjvErrors } from '../../../utils/ajv-errors.js';
import { checkCircularRefsInSchema } from '../../../utils/check-circular-refs-in-schema.js';
import { removeWriteOnlyProperties } from '../../description-parser/index.js';
const ajvStrict = new Ajv({
schemaId: '$id',
meta: true,
allErrors: true,
strictSchema: false,
inlineRefs: false,
validateSchema: false,
discriminator: true,
allowUnionTypes: true,
validateFormats: false,
logger: false,
verbose: true,
defaultUnevaluatedProperties: false,
});
export function checkSchema({ stepCallCtx, descriptionOperation, ctx, }) {
const { $response } = stepCallCtx;
const checks = [];
// if no $response, that is a common case for executing dependsOn workflow steps of workflow inside
// the step - return checks
if (!$response || !descriptionOperation) {
return checks;
}
checkStatusCodeFromDescription({ checks, descriptionOperation, $response, ctx });
checkContentTypeFromDescription({ checks, descriptionOperation, $response, ctx });
checkSchemaFromDescription({ checks, descriptionOperation, $response, ctx });
return checks;
}
function checkSchemaFromDescription({ checks, descriptionOperation, $response, ctx, }) {
const { body: resultBody } = $response;
const descriptionResponseByCode = descriptionOperation?.responses[String($response?.statusCode)] ||
descriptionOperation?.responses['default'];
const schemaFromDescription = $response?.contentType
? descriptionResponseByCode?.content?.[$response.contentType]?.schema
: undefined;
const isSchemaWithCircularRef = checkCircularRefsInSchema(schemaFromDescription);
if (isSchemaWithCircularRef) {
ctx.options.logger.output(`${yellow('WARNING: schema have circular references')}`);
ctx.options.logger.printNewLine();
}
if (schemaFromDescription && !isSchemaWithCircularRef) {
try {
checks.push({
name: CHECKS.SCHEMA_CHECK,
passed: ajvStrict.validate(removeWriteOnlyProperties(schemaFromDescription), resultBody),
message: ajvStrict.errors
? printAjvErrors(removeWriteOnlyProperties(schemaFromDescription), resultBody, ajvStrict.errors)
: '',
severity: ctx.severity['SCHEMA_CHECK'],
});
}
catch (error) {
checks.push({
name: CHECKS.SCHEMA_CHECK,
passed: false,
message: `Ajv error: ${error.message}`,
severity: ctx.severity['SCHEMA_CHECK'],
});
}
}
}
function checkStatusCodeFromDescription({ checks, descriptionOperation, $response, ctx, }) {
const responseStatusCode = $response?.statusCode;
const responseCodesFromDescription = Object.keys(descriptionOperation?.responses || {});
const matchesCodeFromDescription = responseStatusCode &&
responseCodesFromDescription
.map((item) => item.toString())
.includes(responseStatusCode.toString());
const matchesDefaultResponse = responseCodesFromDescription.includes('default');
const message = matchesCodeFromDescription
? dim(`List of valid response codes are inferred from description \n\n`) +
diffLinesUnified(responseCodesFromDescription.map(String), [`${responseStatusCode}`])
: ''; // NOTE: we don't show any diff if response code hits default response
const passed = matchesCodeFromDescription || matchesDefaultResponse;
checks.push({
name: CHECKS.STATUS_CODE_CHECK,
passed,
message,
...(passed && {
condition: `$statusCode in [${responseCodesFromDescription.join(', ')}]`,
}),
severity: ctx.severity['STATUS_CODE_CHECK'],
});
}
function checkContentTypeFromDescription({ checks, descriptionOperation, $response, ctx, }) {
const statusCode = $response?.statusCode;
const responseContentType = $response?.contentType;
const possibleContentTypesFromDescription = Object.keys(descriptionOperation?.responses[statusCode]?.content || {});
if (!possibleContentTypesFromDescription.length) {
return;
}
if (responseContentType && !possibleContentTypesFromDescription.includes(responseContentType)) {
checks.push({
name: CHECKS.CONTENT_TYPE_CHECK,
passed: false,
message: `Content type ${red(responseContentType)} for ${blue(statusCode)} response is not described in the schema.
Expected content types: ${blue(possibleContentTypesFromDescription.join(', '))}.`,
severity: ctx.severity['CONTENT_TYPE_CHECK'],
});
}
else {
checks.push({
name: CHECKS.CONTENT_TYPE_CHECK,
passed: true,
message: `Content type "${responseContentType}" is described in the schema.`,
severity: ctx.severity['CONTENT_TYPE_CHECK'],
});
}
}
//# sourceMappingURL=schema-checker.js.map