import { JsonValidationService } from './JsonValidationService'; import { SchemaValidationError } from './errors/SchemaValidationError'; import validManifest from './manifest/v1/__test__/schemas/validComponent.json'; import validManifestJson from './manifest/v1/__test__/schemas/validComponentJson.json'; import inputStringWithFormat from './manifest/v1/__test__/schemas/inputStringWithFormat.json'; import invalidUiMetadata from './manifest/v1/__test__/schemas/invalidUiMetadata.json'; function expectToThrowErrorMatchingTypeAndMessage( // eslint-disable-next-line @typescript-eslint/ban-types received: Function, // eslint-disable-next-line @typescript-eslint/ban-types errorType: Function, message: string, validationExpected?: any, ) { let error: null | SchemaValidationError = null; try { received(); } catch (e: any) { error = e; } expect(error).toBeDefined(); expect(error?.message).toEqual(message); expect(error).toBeInstanceOf(errorType); expect(error?.validationData).toEqual(validationExpected); } describe('JsonValidationService', () => { const jsonValidationService: JsonValidationService = new JsonValidationService(); describe('validateManifest', () => { it('should return true for a valid JSON endpoint manifest', () => { const result = jsonValidationService.validateManifest(validManifestJson, 'v1'); expect(result).toBe(true); }); it('should return true for a valid manifest', () => { const result = jsonValidationService.validateManifest(validManifest, 'v1'); expect(result).toBe(true); }); it('should throw a SchemaValidationError for an invalid manifest', () => { const invalidManifest = { ...validManifest }; delete (invalidManifest as any).name; expectToThrowErrorMatchingTypeAndMessage( () => { jsonValidationService.validateManifest(invalidManifest, 'v1'); }, SchemaValidationError, `failed validation: The required property \`name\` is missing at \`#\``, { '#': [ { data: { key: 'name', pointer: '#', }, message: `The required property \`name\` is missing at \`#\``, }, ], }, ); }); it('should throw an error for an invalid manifest version', () => { expectToThrowErrorMatchingTypeAndMessage( () => { jsonValidationService.validateManifest(validManifest, 'invalid-version' as any); }, SchemaValidationError, 'failed validation: Invalid manifest version', ); }); it('should return true for a valid manifest with a string format MatrixAssetUri', () => { const result = jsonValidationService.validateManifest(inputStringWithFormat, 'v1'); expect(result).toBe(true); }); it('should throw handle multiple SchemaValidationError for an invalid manifest in validationError Object', () => { const invalidManifest = { ...validManifest }; delete (invalidManifest as any).name; delete (invalidManifest as any).version; expectToThrowErrorMatchingTypeAndMessage( () => { jsonValidationService.validateManifest(invalidManifest, 'v1'); }, SchemaValidationError, `failed validation: The required property \`name\` is missing at \`#\`,\nThe required property \`version\` is missing at \`#\``, { '#': [ { data: { key: 'name', pointer: '#', }, message: `The required property \`name\` is missing at \`#\``, }, { data: { key: 'version', pointer: '#', }, message: `The required property \`version\` is missing at \`#\``, }, ], }, ); }); it('should throw error for invalid ui:metadata options', () => { expectToThrowErrorMatchingTypeAndMessage( () => { jsonValidationService.validateManifest(invalidUiMetadata, 'v1'); }, SchemaValidationError, 'failed validation: ui:metadata property quickOption is only valid for boolean or enum based properties.', { '#/functions/0/input/properties/prop': [ { data: { expected: 'enum', pointer: '#/functions/0/input/properties/prop', received: 'object', value: { type: 'string', 'ui:metadata': { quickOption: true } }, }, message: 'ui:metadata property quickOption is only valid for boolean or enum based properties.', }, ], }, ); }); }); describe('validateRenderInput', () => { describe('matrix-asset-uri input', () => { const functionInputSchema = { type: 'object', properties: { 'matrix-asset': { type: 'string', format: 'matrix-asset-uri', matrixAssetTypes: ['image'] }, }, required: ['matrix-asset'], }; it('should return true for valid a string with matrix-asset-uri format', () => { const validMatrixAssetUri = 'matrix-asset://canary.uat.matrix.squiz.cloud/123'; expect( jsonValidationService.validateRenderInput(functionInputSchema, { 'matrix-asset': validMatrixAssetUri, }), ).toEqual(true); }); it('should throw error for invalid matrix-asset-uri format', () => { const invalidMatrixAssetUri = 'not-valid://canary uat matrix.squiz.cloud//abc123'; expectToThrowErrorMatchingTypeAndMessage( () => { jsonValidationService.validateRenderInput(functionInputSchema, { 'matrix-asset': invalidMatrixAssetUri, }); }, SchemaValidationError, 'failed validation: Value matrix-asset-uri (not-valid://canary uat matrix.squiz.cloud//abc123) in `#/matrix-asset` is not a valid matrix asset uri', { '#/matrix-asset': [ { data: { errors: { assetId: { data: { expected: /^\d+(?::.+)?$/, received: '/abc123' }, message: 'Matrix Asset Id has invalid format, must match /^d+(?::.+)?$/', }, identifier: { data: { expected: /^[a-zA-Z0-9.-]+$/, received: 'canary uat matrix.squiz.cloud' }, message: 'Matrix Identifier has invalid format, must match /^[a-zA-Z0-9.-]+$/', }, scheme: { data: { expected: 'matrix-asset', received: 'not-valid' }, message: 'Uri scheme is invalid, must match "matrix-asset"', }, }, pointer: '#/matrix-asset', value: 'not-valid://canary uat matrix.squiz.cloud//abc123', }, message: 'Value matrix-asset-uri (not-valid://canary uat matrix.squiz.cloud//abc123) in `#/matrix-asset` is not a valid matrix asset uri', }, ], }, ); }); it('should throw error for invalid matrix-asset-uri type number', () => { const invalidMatrixAssetUri = 1234; expectToThrowErrorMatchingTypeAndMessage( () => { jsonValidationService.validateRenderInput(functionInputSchema, { 'matrix-asset': invalidMatrixAssetUri, }); }, SchemaValidationError, 'failed validation: Expected `1234` (number) in `#/matrix-asset` to be of type `string`', { '#/matrix-asset': [ { data: { expected: 'string', pointer: '#/matrix-asset', received: 'number', value: 1234 }, message: 'Expected `1234` (number) in `#/matrix-asset` to be of type `string`', }, ], }, ); }); it('should throw error for invalid matrix-asset-uri which does not contain correct number of parts', () => { const invalidMatrixAssetUri = 'matrix://'; expectToThrowErrorMatchingTypeAndMessage( () => { jsonValidationService.validateRenderInput(functionInputSchema, { 'matrix-asset': invalidMatrixAssetUri, }); }, SchemaValidationError, 'failed validation: Value matrix-asset-uri (matrix://) in `#/matrix-asset` is not a valid matrix asset uri', { '#/matrix-asset': [ { data: { errors: { assetId: { data: { expected: /^\d+(?::.+)?$/, received: '' }, message: 'Matrix Asset Id has invalid format, must match /^d+(?::.+)?$/', }, identifier: { data: { expected: /^[a-zA-Z0-9.-]+$/, received: '' }, message: 'Matrix Identifier has invalid format, must match /^[a-zA-Z0-9.-]+$/', }, scheme: { data: { expected: 'matrix-asset', received: 'matrix' }, message: 'Uri scheme is invalid, must match "matrix-asset"', }, }, pointer: '#/matrix-asset', value: 'matrix://', }, message: 'Value matrix-asset-uri (matrix://) in `#/matrix-asset` is not a valid matrix asset uri', }, ], }, ); }); }); it('should validate a property with type FormattedText as string', () => { const schema = { type: 'object', properties: { 'my-input': { type: 'FormattedText' }, }, required: ['my-input'], }; const value = { 'my-input': 'hello', }; expect(jsonValidationService.validateRenderInput(schema, value)).toEqual(true); }); it('should error when a property with type FormattedText is provided a valid FormattedText value', () => { const schema = { type: 'object', properties: { 'my-input': { type: 'FormattedText' }, }, required: ['my-input'], }; //check that the error contains the data const value = { 'my-input': [{ type: 'text', value: 'hello' }], }; expect(() => jsonValidationService.validateRenderInput(schema, value)).toThrowError(SchemaValidationError); }); it('should validate a property with type FormattedText within an if/then/else as string', () => { const schema = { type: 'object', properties: { 'my-input': { type: 'object', properties: { prop: { type: 'string' }, }, required: ['prop'], if: { properties: { prop: { const: 'a' } } }, then: { properties: { text: { type: 'FormattedText' }, }, required: ['text'], }, else: { properties: { text: { type: 'array', items: { type: 'FormattedText' } }, }, required: ['text'], }, }, }, required: ['my-input'], }; const value = { 'my-input': { prop: 'a', text: 'hello', }, }; expect(jsonValidationService.validateRenderInput(schema, value)).toEqual(true); const otherValue = { 'my-input': { prop: 'b', text: ['my', 'formatted', 'text'], }, }; expect(jsonValidationService.validateRenderInput(schema, otherValue)).toEqual(true); }); }); });