I have a class that allows me to validate API responses against schemas using AJV. It includes the _definitionsHelper object that stores reusable blocks of schema:
import Ajv from 'ajv';
export default class SchemaUtilities {
validateSchema = (schema, response) => {
const ajv = new Ajv();
const validate = ajv.addSchema(this._definitionsHelper).compile(schema);
const valid = validate(response);
if (!valid) {
this._getSchemaError(validate.errors).then((schemaError) => {
throw new Error(`Schema validation failed. ${schemaError}`);
});
} else {
cy.log('Schema validated.');
}
};
private _definitionsHelper = {
$id: 'customDefinitions',
definitions: {
baseResponseProperties: {
type: 'object',
properties: {
errorCode: {
type: 'number'
},
responseCode: {
type: 'number'
},
},
required: ['errorCode', 'responseCode']
},
FOO: {
type: 'object',
properties: {
id: { type: 'number' },
definition: { type: 'number' },
url: { type: 'string' }
},
required: ['id', 'definition', 'url']
},
BAR: {
type: 'object',
properties: {
featureId: { type: 'number' },
order: { type: 'number' },
featureImageUrl: { type: 'string' },
navigationLinkType: { type: 'string' },
navigationLink: { type: ['string', 'null'] },
FOO: { type: ['object', 'null'], items: { $ref: 'customDefinitions#/definitions/FOO' } },
sku: { type: ['string', 'null'] },
yyyUrl: { type: ['string', 'null'] },
featureYYYAlignment: { type: ['string', 'null'] }
},
required: ['featureId', 'order', 'featureImageUrl', 'navigationLinkType', 'navigationLink', 'FOO', 'sku', 'yyyUrl', 'featureYYYAlignment']
}
},
};
private _getSchemaError = (getAjvError) => {
return cy.wrap(`Field: ${getAjvError[0]['instancePath']} is invalid. Cause: ${getAjvError[0]['message'].replace(',', ', found ')}`);
};
}
In BAR, I set FOO's type to ['object', 'null'] and removed one of the properties from my mock JSON response.
Mock JSON response (having removed the required 'url' property from FOO):
{
"errorCode": 0,
"responseCode": 0,
"BAR": [{
"featureId": 2,
"order": 2,
"featureImageUrl": "x",
"navigationLinkType": "x",
"navigationLink": null,
"FOO": {
"id": 2,
"definition": 2
},
"sku": "x",
"yyyUrl": null,
"featureYYYAlignment": "x"
}]
}
Expected result: an error should be thrown advising that the removed property is required.
Actual result: successfully validates.
Troubleshooting:
Removed properties from the root of the mock JSON response to ensure AJV is correctly erroring when required properties are missing. It is.
In
_definitionsHelper, if I change FOO's type to'array'or['array', 'null']and in the mock JSON response, I wrap FOO's object in an array, AJV throws the expected error. But this is of no use to me because the API returns FOO as an object, not an array with an object inside it.
The schema file that's validated against the mock JSON response:
export const featuresSchema = {
type: 'object',
$ref: 'customDefinitions#/definitions/baseResponseProperties',
properties: {
BAR: { type: 'array', items: { $ref: 'customDefinitions#/definitions/BAR' } }
},
required: ['BAR']
};
I've read Stackoverflow's suggested threads but couldn't find one that matches this issue.
Thank you for any help you can provide.
Depends on the JSON Schema version used which you didn't indicate, AJV defaults to draft-07 with the
importstatement you are usingCouple things:
$refin older draft versions of JSON Schema. So the$refdefined withbaseResponsePropertiesis validated but theBARschema is ignored, thus why you're not returning the expected error.Try this.
The second issue is
FOOis only defined withtype: ['object', 'null']and you have anitemskeyword defined. Theseitemswill never be evaluated because you are constraining the schema to only the two defined types.