Spaces:
Sleeping
Sleeping
| import Ajv from 'ajv'; | |
| import { JSONSchema6 } from 'json-schema'; | |
| import { betterAjvErrors } from './index'; | |
| describe('betterAjvErrors', () => { | |
| let ajv: Ajv; | |
| let schema: JSONSchema6; | |
| let data: Record<string, unknown>; | |
| beforeEach(() => { | |
| ajv = new Ajv({ allErrors: true }); | |
| schema = { | |
| type: 'object', | |
| required: ['str'], | |
| properties: { | |
| str: { | |
| type: 'string', | |
| }, | |
| enum: { | |
| type: 'string', | |
| enum: ['one', 'two'], | |
| }, | |
| bounds: { | |
| type: 'number', | |
| minimum: 2, | |
| maximum: 4, | |
| }, | |
| nested: { | |
| type: 'object', | |
| required: ['deepReq'], | |
| properties: { | |
| deepReq: { | |
| type: 'boolean', | |
| }, | |
| deep: { | |
| type: 'string', | |
| }, | |
| }, | |
| additionalProperties: false, | |
| }, | |
| }, | |
| additionalProperties: false, | |
| }; | |
| }); | |
| describe('combined schemas', () => { | |
| it('should handle type errors', () => { | |
| data = { | |
| str: 123, | |
| }; | |
| const combinedSchema = {type: 'boolean'}; | |
| const validateCombined = ajv.addSchema(combinedSchema).compile(schema); | |
| validateCombined(data); | |
| const betterErrors = betterAjvErrors({ data, schema, errors: validateCombined.errors }); | |
| expect(betterErrors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'type', | |
| }, | |
| message: "'str' property type must be string", | |
| path: '{base}.str', | |
| }, | |
| ]); | |
| }); | |
| }); | |
| describe('additionalProperties', () => { | |
| it('should handle additionalProperties=false', () => { | |
| data = { | |
| str: 'str', | |
| foo: 'bar', | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'additionalProperties', | |
| }, | |
| message: "'foo' property is not expected to be here", | |
| path: '{base}', | |
| }, | |
| ]); | |
| }); | |
| it('should handle additionalProperties=true', () => { | |
| data = { | |
| str: 'str', | |
| foo: 'bar', | |
| }; | |
| schema.additionalProperties = true; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([]); | |
| }); | |
| it('should give suggestions when relevant', () => { | |
| data = { | |
| str: 'str', | |
| bonds: 'bar', | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'additionalProperties', | |
| }, | |
| message: "'bonds' property is not expected to be here", | |
| path: '{base}', | |
| suggestion: "Did you mean property 'bounds'?", | |
| }, | |
| ]); | |
| }); | |
| it('should handle object schemas without properties', () => { | |
| data = { | |
| empty: { foo: 1 }, | |
| }; | |
| schema = { | |
| type: 'object', | |
| properties: { | |
| empty: { | |
| type: 'object', | |
| additionalProperties: false, | |
| }, | |
| }, | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'additionalProperties', | |
| }, | |
| message: "'foo' property is not expected to be here", | |
| path: '{base}.empty', | |
| }, | |
| ]); | |
| }); | |
| }); | |
| describe('required', () => { | |
| it('should handle required properties', () => { | |
| data = { | |
| nested: {}, | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'required', | |
| }, | |
| message: "{base} must have required property 'str'", | |
| path: '{base}', | |
| }, | |
| { | |
| context: { | |
| errorType: 'required', | |
| }, | |
| message: "{base}.nested must have required property 'deepReq'", | |
| path: '{base}.nested', | |
| }, | |
| ]); | |
| }); | |
| it('should handle multiple required properties', () => { | |
| schema = { | |
| type: 'object', | |
| required: ['req1', 'req2'], | |
| properties: { | |
| req1: { | |
| type: 'string', | |
| }, | |
| req2: { | |
| type: 'string', | |
| }, | |
| }, | |
| }; | |
| data = {}; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'required', | |
| }, | |
| message: "{base} must have required property 'req1'", | |
| path: '{base}', | |
| }, | |
| { | |
| context: { | |
| errorType: 'required', | |
| }, | |
| message: "{base} must have required property 'req2'", | |
| path: '{base}', | |
| }, | |
| ]); | |
| }); | |
| }); | |
| describe('type', () => { | |
| it('should handle type errors', () => { | |
| data = { | |
| str: 123, | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'type', | |
| }, | |
| message: "'str' property type must be string", | |
| path: '{base}.str', | |
| }, | |
| ]); | |
| }); | |
| }); | |
| describe('minimum/maximum', () => { | |
| it('should handle minimum/maximum errors', () => { | |
| data = { | |
| str: 'str', | |
| bounds: 123, | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'maximum', | |
| }, | |
| message: "property 'bounds' must be <= 4", | |
| path: '{base}.bounds', | |
| }, | |
| ]); | |
| }); | |
| }); | |
| describe('enum', () => { | |
| it('should handle enum errors', () => { | |
| data = { | |
| str: 'str', | |
| enum: 'zzzz', | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'enum', | |
| allowedValues: ['one', 'two'], | |
| }, | |
| message: "'enum' property must be equal to one of the allowed values", | |
| path: '{base}.enum', | |
| }, | |
| ]); | |
| }); | |
| it('should provide suggestions when relevant', () => { | |
| data = { | |
| str: 'str', | |
| enum: 'pne', | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'enum', | |
| allowedValues: ['one', 'two'], | |
| }, | |
| message: "'enum' property must be equal to one of the allowed values", | |
| path: '{base}.enum', | |
| suggestion: "Did you mean 'one'?", | |
| }, | |
| ]); | |
| }); | |
| it('should not crash when allowedValues contains null', () => { | |
| data = { | |
| str: 'str', | |
| enum: 'invalid', | |
| }; | |
| schema = { | |
| type: 'object', | |
| properties: { | |
| str: { type: 'string' }, | |
| enum: { | |
| type: ['string', 'null'], | |
| enum: ['one', 'two', null], | |
| }, | |
| }, | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'enum', | |
| allowedValues: ['one', 'two', null], | |
| }, | |
| message: "'enum' property must be equal to one of the allowed values", | |
| path: '{base}.enum', | |
| suggestion: "Did you mean 'one'?", | |
| }, | |
| ]); | |
| }); | |
| it('should not crash on null value', () => { | |
| data = { | |
| type: null, | |
| }; | |
| schema = { | |
| type: 'object', | |
| properties: { | |
| type: { | |
| type: 'string', | |
| enum: ['primary', 'secondary'], | |
| }, | |
| }, | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| allowedValues: ['primary', 'secondary'], | |
| errorType: 'enum', | |
| }, | |
| message: "'type' property must be equal to one of the allowed values", | |
| path: '{base}.type', | |
| }, | |
| ]); | |
| }); | |
| }); | |
| it('should handle array paths', () => { | |
| data = { | |
| custom: [{ foo: 'bar' }, { aaa: 'zzz' }], | |
| }; | |
| schema = { | |
| type: 'object', | |
| properties: { | |
| custom: { | |
| type: 'array', | |
| items: { | |
| type: 'object', | |
| additionalProperties: false, | |
| properties: { | |
| id: { | |
| type: 'string', | |
| }, | |
| title: { | |
| type: 'string', | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'additionalProperties', | |
| }, | |
| message: "'foo' property is not expected to be here", | |
| path: '{base}.custom.0', | |
| }, | |
| { | |
| context: { | |
| errorType: 'additionalProperties', | |
| }, | |
| message: "'aaa' property is not expected to be here", | |
| path: '{base}.custom.1', | |
| }, | |
| ]); | |
| }); | |
| it('should handle file $refs', () => { | |
| data = { | |
| child: [{ foo: 'bar' }, { aaa: 'zzz' }], | |
| }; | |
| schema = { | |
| $id: 'http://example.com/schemas/Main.json', | |
| type: 'object', | |
| properties: { | |
| child: { | |
| type: 'array', | |
| items: { | |
| $ref: './Child.json', | |
| }, | |
| }, | |
| }, | |
| }; | |
| ajv.addSchema({ | |
| $id: 'http://example.com/schemas/Child.json', | |
| additionalProperties: false, | |
| type: 'object', | |
| properties: { | |
| id: { | |
| type: 'string', | |
| }, | |
| }, | |
| }); | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| errorType: 'additionalProperties', | |
| }, | |
| message: "'foo' property is not expected to be here", | |
| path: '{base}.child.0', | |
| }, | |
| { | |
| context: { | |
| errorType: 'additionalProperties', | |
| }, | |
| message: "'aaa' property is not expected to be here", | |
| path: '{base}.child.1', | |
| }, | |
| ]); | |
| }); | |
| it('should handle number enums', () => { | |
| data = { | |
| isLive: 2, | |
| }; | |
| schema = { | |
| type: 'object', | |
| properties: { | |
| isLive: { | |
| type: 'integer', | |
| enum: [0, 1], | |
| }, | |
| }, | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| allowedValues: [0, 1], | |
| errorType: 'enum', | |
| }, | |
| message: "'isLive' property must be equal to one of the allowed values", | |
| path: '{base}.isLive', | |
| }, | |
| ]); | |
| }); | |
| describe('const', () => { | |
| it('should handle const errors', () => { | |
| data = { | |
| const: 2, | |
| }; | |
| schema = { | |
| type: 'object', | |
| properties: { | |
| const: { | |
| type: 'integer', | |
| const: 42, | |
| }, | |
| }, | |
| }; | |
| ajv.validate(schema, data); | |
| const errors = betterAjvErrors({ data, schema, errors: ajv.errors }); | |
| expect(errors).toEqual([ | |
| { | |
| context: { | |
| allowedValue: 42, | |
| errorType: 'const', | |
| }, | |
| message: "'const' property must be equal to the allowed value", | |
| path: '{base}.const', | |
| }, | |
| ]); | |
| }); | |
| }); | |
| }); | |