From bbbe35cd467eeb37ae06defa610ce6f0b1ce74d9 Mon Sep 17 00:00:00 2001 From: Kistl Date: Fri, 27 Feb 2026 19:58:56 +0100 Subject: [PATCH 1/4] Enhance get-feature-flag-request.v1.schema Additional changes: - Fix outlets-request-dynamic-context-response.v1.schema.ts schema - Add unit test to make sure that all schemas are valid - Enhance model OutletsRequestContextResponse and OutletsRequestDynamicContextResponse by defining property plugin(s) better - Update typescript version to avoid warnings during execution of unit tests --- .gitignore | 3 + CHANGELOG.md | 11 + docs/validation.md | 16 + karma.conf.js | 5 +- package-lock.json | 14 +- package.json | 5 +- .../outlets-request-context-response.model.ts | 12 +- ...-request-dynamic-context-response.model.ts | 12 +- .../get-feature-flag-request.v1.schema.ts | 42 +- ...lets-request-context-response.v1.schema.ts | 31 +- ...uest-dynamic-context-response.v1.schema.ts | 51 ++- src/validation/schemas/schemas.spec.ts | 423 ++++++++++++++++++ 12 files changed, 596 insertions(+), 29 deletions(-) create mode 100644 src/validation/schemas/schemas.spec.ts diff --git a/.gitignore b/.gitignore index 233ea32..db8dc5f 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,6 @@ mocha.json #cypress video folder cypress/videos + +# vscode settings folder +.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index c3b5bcc..ece0300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.21.0] - 2026-03-03 + +### Changed + +- Fix schema outletsRequestDynamicContextResponse_v1_schema to apply draft-06 and draft-07 JSON validation standards +- Enhance schema getFeatureFlagRequest_v1_schema to support array next to object as correct request payload +- Add unit test to make sure that all schemas are valid and follow draft-04, draft-06 and draft-07 JSON validation standards (besides outletsRequestDynamicContextResponse_v1_schema) + - outletsRequestDynamicContextResponse_v1_schema has an optional array property called "plugins", which contains objects with an optional array property called "sandboxPolicies". Draft-04 does not support this "optional array" inside "optional array", while draft-06 and draft-07 do. +- Enhance model OutletsRequestContextResponse and OutletsRequestDynamicContextResponse by defining property plugin(s) better +- Update typescript version to avoid warnings during execution of unit tests + ## [1.20.0] - 2024-10-10 ### Added diff --git a/docs/validation.md b/docs/validation.md index f298646..e1ebf1a 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -56,6 +56,17 @@ where: details describing validation error. It is up to consuming application which additional information include in this property. +**Important:** Most schemas within fsm-shell support the following JSON Schema standards: +- draft-04 +- draft-06 +- draft-07 + +The only current exception is outletsRequestDynamicContextResponse_v1_schema, which supports: +- draft-06 +- draft-07 + +We recommend using an external JSON Schema validation library that supports at least draft-07. Please note that future schemas may no longer support draft-04. + ## Enabling validation To enable validation the consumer of fsm-shell library should call `setValidator` method on ShellSdk instance. @@ -121,3 +132,8 @@ export class ShellPayloadValidationService implements PayloadValidator { } ``` + +**Hint:** If Ajv is use, it is recommended to use v6 as it supports the following JSON Schema standards: +- draft-04 +- draft-06 +- draft-07 diff --git a/karma.conf.js b/karma.conf.js index 045552b..f43f1b4 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,10 +1,11 @@ - +// "resolveJsonModule": true is required to load json files in tests, e.g. meta schemas (see SchemaValidation.spec.ts) const tsconfig = { "compilerOptions": { "lib": [ "es2015", "dom" - ] + ], + "resolveJsonModule": true }, "reports": { "html": "coverage", diff --git a/package-lock.json b/package-lock.json index 30114d5..43275af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "fsm-shell", - "version": "1.20.0", + "version": "1.21.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fsm-shell", - "version": "1.20.0", + "version": "1.21.0", "license": "Apache-2.0", "devDependencies": { "@types/jasmine": "^3.5.12", "@types/jasminewd2": "^2.0.8", "@types/sinon": "^9.0.4", + "ajv": "~6.12.6", "copyfiles": "^2.3.0", "coveralls": "^3.1.0", "cypress": "^4.12.1", @@ -43,7 +44,7 @@ "start-server-and-test": "^1.12.0", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", - "typescript": "~3.1.6" + "typescript": "~3.9.10" } }, "node_modules/@babel/code-frame": { @@ -10624,10 +10625,11 @@ } }, "node_modules/typescript": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.8.tgz", - "integrity": "sha512-R97qglMfoKjfKD0N24o7W6bS+SwjN/eaQNIaxR8S5HdLRnt7rCk6LCmE3tve1KN8gXKgbJU51aZHRRMAQcIbMA==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 0073aef..7051a7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fsm-shell", - "version": "1.20.0", + "version": "1.21.0", "description": "client library for FSM shell", "main": "release/fsm-shell-client.js", "module": "release/fsm-shell-client.es.js", @@ -38,6 +38,7 @@ "@types/jasmine": "^3.5.12", "@types/jasminewd2": "^2.0.8", "@types/sinon": "^9.0.4", + "ajv": "~6.12.6", "copyfiles": "^2.3.0", "coveralls": "^3.1.0", "cypress": "^4.12.1", @@ -69,7 +70,7 @@ "start-server-and-test": "^1.12.0", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", - "typescript": "~3.1.6" + "typescript": "~3.9.10" }, "dependencies": {}, "husky": { diff --git a/src/models/outlets/outlets-request-context-response.model.ts b/src/models/outlets/outlets-request-context-response.model.ts index 9f556c3..a3c3ee0 100644 --- a/src/models/outlets/outlets-request-context-response.model.ts +++ b/src/models/outlets/outlets-request-context-response.model.ts @@ -2,6 +2,16 @@ export interface OutletsRequestContextResponse { target?: string; isRootNodeHttps?: boolean; isConfigurationMode: boolean; - plugin?: any; + plugin?: PluginForOutlet; isPreviewActive: boolean; } + +interface PluginForOutlet { + name: string; + url: string; + optimalHeight?: string; + useShellSDK?: boolean; + isActive: boolean; + sandboxPolicies?: string[]; + assignmentId?: string; +} diff --git a/src/models/outlets/outlets-request-dynamic-context-response.model.ts b/src/models/outlets/outlets-request-dynamic-context-response.model.ts index 9f58709..102e7f2 100644 --- a/src/models/outlets/outlets-request-dynamic-context-response.model.ts +++ b/src/models/outlets/outlets-request-dynamic-context-response.model.ts @@ -3,6 +3,16 @@ export interface OutletsRequestDynamicContextResponse { isRootNodeHttps?: boolean; isConfigurationMode?: boolean; areDynamicOutletsEnabled?: boolean; - plugins?: any[]; + plugins?: PluginForDynamicOutlet[]; isPreviewActive: boolean; } + +interface PluginForDynamicOutlet { + name: string; + url: string; + optimalHeight?: string; + useShellSDK?: boolean; + isActive: boolean; + sandboxPolicies?: string[]; + assignmentId: string; +} diff --git a/src/validation/schemas/feature-flag/get-feature-flag-request.v1.schema.ts b/src/validation/schemas/feature-flag/get-feature-flag-request.v1.schema.ts index fcb62a6..3415d46 100644 --- a/src/validation/schemas/feature-flag/get-feature-flag-request.v1.schema.ts +++ b/src/validation/schemas/feature-flag/get-feature-flag-request.v1.schema.ts @@ -1,12 +1,36 @@ export const getFeatureFlagRequest_v1_schema = { - type: 'object', - properties: { - key: { - type: 'string', - }, - defaultValue: { - type: 'boolean', - }, + $defs: { + payload: { + type: 'object', + properties: { + key: { + type: 'string' + }, + defaultValue: { + type: 'boolean' + } + }, + required: ['key', 'defaultValue'] + } }, - required: ['key', 'defaultValue'], + anyOf: [ + { + type: 'object', + properties: { + key: { + type: 'string' + }, + defaultValue: { + type: 'boolean' + } + }, + required: ['key', 'defaultValue'] + }, + { + type: 'array', + items: { + $ref: '#/$defs/payload' + } + } + ] }; diff --git a/src/validation/schemas/outlets/outlets-request-context-response.v1.schema.ts b/src/validation/schemas/outlets/outlets-request-context-response.v1.schema.ts index 06ba7ce..23e6228 100644 --- a/src/validation/schemas/outlets/outlets-request-context-response.v1.schema.ts +++ b/src/validation/schemas/outlets/outlets-request-context-response.v1.schema.ts @@ -13,7 +13,36 @@ export const outletsRequestContextResponse_v1_schema = { isPreviewActive: { type: 'boolean', }, - plugin: {}, + plugin: { + type: 'object', + properties: { + name: { + type: 'string' + }, + url: { + type: 'string' + }, + optimalHeight: { + type: 'string' + }, + useShellSDK: { + type: 'boolean' + }, + isActive: { + type: 'boolean' + }, + sandboxPolicies: { + type: 'array', + items: { + type: 'string' + } + }, + assignmentId: { + type: 'string' + } + }, + required: ['name', 'url', 'isActive'] + }, }, required: ['isConfigurationMode'], }; diff --git a/src/validation/schemas/outlets/outlets-request-dynamic-context-response.v1.schema.ts b/src/validation/schemas/outlets/outlets-request-dynamic-context-response.v1.schema.ts index ff24774..3e16066 100644 --- a/src/validation/schemas/outlets/outlets-request-dynamic-context-response.v1.schema.ts +++ b/src/validation/schemas/outlets/outlets-request-dynamic-context-response.v1.schema.ts @@ -1,22 +1,59 @@ export const outletsRequestDynamicContextResponse_v1_schema = { + $defs: { + plugin: { + type: 'object', + properties: { + name: { + type: 'string' + }, + url: { + type: 'string' + }, + optimalHeight: { + type: 'string' + }, + useShellSDK: { + type: 'boolean' + }, + isActive: { + type: 'boolean' + }, + sandboxPolicies: { + type: 'array', + items: { + type: 'string' + } + }, + assignmentId: { + type: 'string' + } + }, + required: ['name', 'url', 'isActive', 'assignmentId'] + } + }, type: 'object', properties: { target: { - type: 'string', + type: 'string' }, isRootNodeHttps: { - type: 'boolean', + type: 'boolean' }, isConfigurationMode: { - type: 'boolean', + type: 'boolean' }, areDynamicOutletsEnabled: { - type: 'boolean', + type: 'boolean' }, isPreviewActive: { - type: 'boolean', + type: 'boolean' }, - plugins: [], + plugins: { + type: 'array', + items: { + $ref: '#/$defs/plugin' + } + } }, - required: [], + required: [] }; diff --git a/src/validation/schemas/schemas.spec.ts b/src/validation/schemas/schemas.spec.ts new file mode 100644 index 0000000..0de220f --- /dev/null +++ b/src/validation/schemas/schemas.spec.ts @@ -0,0 +1,423 @@ +import * as Ajv from 'ajv'; +import * as draft6MetaSchema from 'ajv/lib/refs/json-schema-draft-06.json'; +import * as draft4MetaSchema from 'ajv/lib/refs/json-schema-draft-04.json'; + +import { authRequest_v1_schema } from './authentication/auth-request.v1.schema'; +import { authResponse_v1_schema } from './authentication/auth-response.v1.schema'; + +import { getItemRequest_v1_schema } from './cloud-storage/get-item-request.v1.schema'; +import { getItemRequest_v2_schema } from './cloud-storage/get-item-request.v2.schema'; +import { getItemResponse_v1_schema } from './cloud-storage/get-item-response.v1.schema'; +import { getItemResponse_v2_schema } from './cloud-storage/get-item-response.v2.schema'; +import { setItemRequest_v1_schema } from './cloud-storage/set-item-request.v1.schema'; + +import { getFeatureFlagRequest_v1_schema } from './feature-flag/get-feature-flag-request.v1.schema'; +import { getFeatureFlagResponse_v1_schema } from './feature-flag/get-feature-flag-response.v1.schema'; + +import { setTitleRequest_v1_schema } from './generic/set-title-request.v1.schema'; + +import { modalOpenRequest_v1_schema } from './modal/modal-open-request.v1.schema'; +import { modalOpenRequest_v2_schema } from './modal/modal-open-request.v2.schema'; +import { modalCloseRequest_v1_schema } from './modal/modal-close-request.v1.schema'; + +import { getPermissionsRequest_v1_schema } from './permissions/get-permissions-request.v1.schema'; +import { getPermissionsRequest_v2_schema } from './permissions/get-permissions-request.v2.schema'; +import { getPermissionsRequest_v3_schema } from './permissions/get-permissions-request.v3.schema'; +import { getPermissionsResponse_v1_schema } from './permissions/get-permissions-response.v1.schema'; +import { getPermissionsResponse_v2_schema } from './permissions/get-permissions-response.v2.schema'; +import { getPermissionsResponse_v3_schema } from './permissions/get-permissions-response.v3.schema'; + +import { requireContextRequest_v1_schema } from './require-context/require-context-request.v1.schema'; + +import { getSettingsRequest_v1_schema } from './settings/get-settings-request.v1.schema'; +import { getSettingsResponse_v1_schema } from './settings/get-settings-response.v1.schema'; + +import { setViewStateRequest_v1_schema } from './view-state/set-view-state-request.v1.schema'; +import { setViewStateResponse_v1_schema } from './view-state/set-view-state-response.v1.schema'; + +import { outletsRequestContextRequest_v1_schema } from './outlets/outlets-request-context-request.v1.schema'; +import { outletsRequestContextResponse_v1_schema } from './outlets/outlets-request-context-response.v1.schema'; +import { outletsAddPluginRequest_v1_schema } from './outlets/outlets-add-plugin-request.v1.schema'; +import { outletsRemovePluginRequest_v1_schema } from './outlets/outlets-remove-plugin-request.v1.schema'; +import { outletsRequestDynamicContextRequest_v1_schema } from './outlets/outlets-request-dynamic-context-request.v1.schema'; +import { outletsRequestDynamicContextResponse_v1_schema } from './outlets/outlets-request-dynamic-context-response.v1.schema'; + +// Valid objects for each schema +export const validAuthRequest_v1 = { response_type: 'token' }; +export const validAuthResponse_v1 = { access_token: 'string', expires_in: 123, token_type: 'string' }; +export const validGetItemRequest_v1 = 'string'; +export const validGetItemRequest_v2 = 'string'; +export const validGetItemResponse_v2 = { key: 'string', value: {} }; +export const validSetItemRequest_v1 = { key: 'string', value: {} }; +export const validGetFeatureFlagRequest_v1 = [{ key: 'string', defaultValue: true }]; +export const validGetFeatureFlagResponse_v1 = { key: 'string', value: true }; +export const validSetTitleRequest_v1 = { title: 'string' }; +export const validModalOpenRequest_v1 = { url: 'string', modalSettings: { title: 'string', size: 'l', backdropClickCloseable: true, isScrollbarHidden: true }, data: {} }; +export const validModalOpenRequest_v2 = { + url: 'string', + modalSettings: { + title: 'string', + showTitleHeader: true, + hasBackdrop: true, + backdropClickCloseable: true, + escKeyCloseable: true, + focusTrapped: true, + fullScreen: true, + mobile: true, + mobileOuterSpacing: true, + draggable: true, + resizable: true, + width: 'string', + height: 'string', + minHeight: 'string', + maxHeight: 'string', + minWidth: 'string', + maxWidth: 'string', + isScrollbarHidden: true + }, + data: {} +}; +export const validModalCloseRequest_v1 = {}; +export const validGetPermissionsRequest_v1 = { objectName: 'string', owners: ['string'] }; +export const validGetPermissionsRequest_v2 = { objectName: 'string', owners: ['string'] }; +export const validGetPermissionsRequest_v3 = { objectName: 'string' }; +export const validGetPermissionsResponse_v1 = { CREATE: true, READ: true, UPDATE: true, DELETE: true, UI_PERMISSIONS: [1] }; +export const validGetPermissionsResponse_v2 = { objectName: 'string', owners: ['string'], permission: validGetPermissionsResponse_v1 }; +export const validGetPermissionsResponse_v3 = { objectName: 'string', permission: validGetPermissionsResponse_v1 }; +export const validRequireContextRequest_v1 = { + clientIdentifier: 'string', + clientSecret: 'string', + cloudStorageKeys: ['string'], + auth: validAuthRequest_v1, + targetOutletName: 'string', + targetExtensionAssignmentId: 'string' +}; +export const validGetSettingsRequest_v1 = 'string'; +export const validGetSettingsResponse_v1 = { key: 'string', value: {} }; +export const validSetViewStateRequest_v1 = { key: 'string', value: {} }; +export const validSetViewStateResponse_v1 = { key: 'string', value: {} }; +export const validOutletsRequestContextRequest_v1 = { target: 'string', assignmentId: 'string', showMocks: true, outletSettings: {} }; +export const validOutletsRequestContextResponse_v1 = { + target: 'string', + isRootNodeHttps: true, + isConfigurationMode: true, + isPreviewActive: true, + plugin: { + name: 'string', + url: 'string', + optimalHeight: 'string', + useShellSDK: true, + isActive: true, + sandboxPolicies: ['string'], + assignmentId: 'string' + } +}; +export const validOutletsAddPluginRequest_v1 = { target: 'string' }; +export const validOutletsRemovePluginRequest_v1 = { target: 'string' }; +export const validOutletsRequestDynamicContextRequest_v1 = { target: 'string', outletSettings: {} }; +export const validOutletsRequestDynamicContextResponse_v1 = { + target: 'string', + isRootNodeHttps: true, + isConfigurationMode: true, + areDynamicOutletsEnabled: true, + isPreviewActive: true, + plugins: [{ + name: 'string', + url: 'string', + optimalHeight: 'string', + useShellSDK: true, + isActive: true, + sandboxPolicies: ['string'], + assignmentId: 'string' + }] +}; + +// Invalid objects for each schema +export const invalidAuthRequest_v1 = { response_type: 123 }; +export const invalidAuthResponse_v1 = { access_token: 123, expires_in: 'not-a-number', token_type: false }; +export const invalidGetItemRequest_v1 = 123; +export const invalidGetItemRequest_v2 = false; +export const invalidGetItemResponse_v2 = { key: 123 }; +export const invalidSetItemRequest_v1 = { key: 123 }; +export const invalidGetFeatureFlagRequest_v1 = [{ key: 123, defaultValue: 'not-bool' }]; +export const invalidGetFeatureFlagResponse_v1 = { key: 123, value: 'not-bool' }; +export const invalidSetTitleRequest_v1 = { title: 123 }; +export const invalidModalOpenRequest_v1 = { url: 123, modalSettings: { title: 123, size: 'x', backdropClickCloseable: 'no', isScrollbarHidden: 'no' }, data: 123 }; +export const invalidModalOpenRequest_v2 = { + url: 123, + modalSettings: { + title: 123, + showTitleHeader: 'no', + hasBackdrop: 'no', + backdropClickCloseable: 'no', + escKeyCloseable: 'no', + focusTrapped: 'no', + fullScreen: 'no', + mobile: 'no', + mobileOuterSpacing: 'no', + draggable: 'no', + resizable: 'no', + width: 123, + height: 123, + minHeight: 123, + maxHeight: 123, + minWidth: 123, + maxWidth: 123, + isScrollbarHidden: 'no' + }, + data: 123 +}; +export const invalidModalCloseRequest_v1 = []; +export const invalidGetPermissionsRequest_v1 = { objectName: 123, owners: 'not-array' }; +export const invalidGetPermissionsRequest_v2 = { objectName: 123, owners: [123] }; +export const invalidGetPermissionsRequest_v3 = { objectName: 123 }; +export const invalidGetPermissionsResponse_v1 = { CREATE: 'yes', READ: 'yes', UPDATE: 'yes', DELETE: 'yes', UI_PERMISSIONS: ['not-number'] }; +export const invalidGetPermissionsResponse_v2 = { objectName: 123, owners: 'not-array', permission: {} }; +export const invalidGetPermissionsResponse_v3 = { objectName: 123, permission: {} }; +export const invalidRequireContextRequest_v1 = { + clientIdentifier: 123, + clientSecret: 123, + cloudStorageKeys: 'not-array', + auth: {}, + targetOutletName: 123, + targetExtensionAssignmentId: 123 +}; +export const invalidGetSettingsRequest_v1 = 123; +export const invalidGetSettingsResponse_v1 = { key: 123, value: undefined }; +export const invalidSetViewStateRequest_v1 = { key: 123 }; +export const invalidSetViewStateResponse_v1 = { key: 123 }; +export const invalidOutletsRequestContextRequest_v1 = { target: 123, assignmentId: 123, showMocks: 'yes', outletSettings: 123 }; +export const invalidOutletsRequestContextResponse_v1 = { + target: 123, + isRootNodeHttps: 'yes', + isConfigurationMode: 'yes', + isPreviewActive: 'yes', + plugin: { + name: 123, + url: 123, + optimalHeight: 123, + useShellSDK: 'yes', + isActive: 'yes', + sandboxPolicies: [123], + assignmentId: 123 + } +}; +export const invalidOutletsAddPluginRequest_v1 = { target: 123 }; +export const invalidOutletsRemovePluginRequest_v1 = { target: 123 }; +export const invalidOutletsRequestDynamicContextRequest_v1 = { target: 123, outletSettings: 123 }; +export const invalidOutletsRequestDynamicContextResponse_v1 = { + target: 123, + isRootNodeHttps: 'yes', + isConfigurationMode: 'yes', + areDynamicOutletsEnabled: 'yes', + isPreviewActive: 'yes', + plugins: [{ + name: 123, + url: 123, + optimalHeight: 123, + useShellSDK: 'yes', + isActive: 'yes', + sandboxPolicies: [123], + assignmentId: 123 + }] +}; + +describe('Schemas', () => { + + let ajv07 = new Ajv(); // Ajv v6 supports draft-07 by default + let ajv06 = new Ajv({ meta: draft6MetaSchema }); + + (draft4MetaSchema as any).$id = 'http://json-schema.org/draft-04/schema#'; + let ajv04 = new Ajv({ meta: draft4MetaSchema }); + + function validateSchemaHelper(ajv: Ajv.Ajv, schema: object) { + const result = ajv.validateSchema(schema); + // If the schema is invalid, log the errors to help with debugging + if (result === false) { + console.log(JSON.stringify(ajv.errors, null, 2)); + } + expect(result).toBeTrue(); + } + + function validateSchemasSupporting04and06and07(ajv: Ajv.Ajv) { + validateSchemaHelper(ajv, authRequest_v1_schema); + validateSchemaHelper(ajv, authResponse_v1_schema); + validateSchemaHelper(ajv, getItemRequest_v1_schema); + validateSchemaHelper(ajv, getItemRequest_v2_schema); + validateSchemaHelper(ajv, getItemResponse_v1_schema); + validateSchemaHelper(ajv, getItemResponse_v2_schema); + validateSchemaHelper(ajv, setItemRequest_v1_schema); + validateSchemaHelper(ajv, getFeatureFlagRequest_v1_schema); + validateSchemaHelper(ajv, getFeatureFlagResponse_v1_schema); + validateSchemaHelper(ajv, setTitleRequest_v1_schema); + validateSchemaHelper(ajv, modalOpenRequest_v1_schema); + validateSchemaHelper(ajv, modalOpenRequest_v2_schema); + validateSchemaHelper(ajv, modalCloseRequest_v1_schema); + validateSchemaHelper(ajv, getPermissionsRequest_v1_schema); + validateSchemaHelper(ajv, getPermissionsRequest_v2_schema); + validateSchemaHelper(ajv, getPermissionsRequest_v3_schema); + validateSchemaHelper(ajv, getPermissionsResponse_v1_schema); + validateSchemaHelper(ajv, getPermissionsResponse_v2_schema); + validateSchemaHelper(ajv, getPermissionsResponse_v3_schema); + validateSchemaHelper(ajv, requireContextRequest_v1_schema); + validateSchemaHelper(ajv, getSettingsRequest_v1_schema); + validateSchemaHelper(ajv, getSettingsResponse_v1_schema); + validateSchemaHelper(ajv, setViewStateRequest_v1_schema); + validateSchemaHelper(ajv, setViewStateResponse_v1_schema); + validateSchemaHelper(ajv, outletsRequestContextRequest_v1_schema); + validateSchemaHelper(ajv, outletsRequestContextResponse_v1_schema); + validateSchemaHelper(ajv, outletsAddPluginRequest_v1_schema); + validateSchemaHelper(ajv, outletsRemovePluginRequest_v1_schema); + validateSchemaHelper(ajv, outletsRequestDynamicContextRequest_v1_schema); + } + + function validateSchemasSupportingOnly06and07(ajv: Ajv.Ajv) { + // outletsRequestDynamicContextResponse_v1_schema has an optional array property called "plugins", + // which contains objects with an optional array property called "sandboxPolicies". Draft-04 does not + // support this "optional array" inside "optional array", while draft-06 and draft-07 do. + validateSchemaHelper(ajv, outletsRequestDynamicContextResponse_v1_schema); + } + + function validateValidDataAgainstSchemaHelper(ajv: Ajv.Ajv, schema: object, data: any) { + const validationFunction = ajv.compile(schema); + const isValid = validationFunction(data); + + if (isValid === false) { + console.log(JSON.stringify(validationFunction.errors, null, 2)); + } + + expect(isValid).toBeTrue(); + } + + /** + * getItemResponse_v1_schema is an empty schema, which means that any data (valid or invalid) would pass validation against it. + * Therefore, we are not including it in the valid data tests since it would not be meaningful. + */ + function validateValidDataAgainstSchemaSupporting04and06and07(ajv: Ajv.Ajv) { + validateValidDataAgainstSchemaHelper(ajv, authRequest_v1_schema, validAuthRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, authResponse_v1_schema, validAuthResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, getItemRequest_v1_schema, validGetItemRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, getItemRequest_v2_schema, validGetItemRequest_v2); + validateValidDataAgainstSchemaHelper(ajv, getItemResponse_v2_schema, validGetItemResponse_v2); + validateValidDataAgainstSchemaHelper(ajv, setItemRequest_v1_schema, validSetItemRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, getFeatureFlagRequest_v1_schema, validGetFeatureFlagRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, getFeatureFlagResponse_v1_schema, validGetFeatureFlagResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, setTitleRequest_v1_schema, validSetTitleRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, modalOpenRequest_v1_schema, validModalOpenRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, modalOpenRequest_v2_schema, validModalOpenRequest_v2); + validateValidDataAgainstSchemaHelper(ajv, modalCloseRequest_v1_schema, validModalCloseRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v1_schema, validGetPermissionsRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v2_schema, validGetPermissionsRequest_v2); + validateValidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v3_schema, validGetPermissionsRequest_v3); + validateValidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v1_schema, validGetPermissionsResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v2_schema, validGetPermissionsResponse_v2); + validateValidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v3_schema, validGetPermissionsResponse_v3); + validateValidDataAgainstSchemaHelper(ajv, requireContextRequest_v1_schema, validRequireContextRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, getSettingsRequest_v1_schema, validGetSettingsRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, getSettingsResponse_v1_schema, validGetSettingsResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, setViewStateRequest_v1_schema, validSetViewStateRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, setViewStateResponse_v1_schema, validSetViewStateResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, outletsRequestContextRequest_v1_schema, validOutletsRequestContextRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, outletsRequestContextResponse_v1_schema, validOutletsRequestContextResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, outletsAddPluginRequest_v1_schema, validOutletsAddPluginRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, outletsRemovePluginRequest_v1_schema, validOutletsRemovePluginRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, outletsRequestDynamicContextRequest_v1_schema, validOutletsRequestDynamicContextRequest_v1); + } + + function validateValidDataAgainstSchemaSupportingOnly06and07(ajv: Ajv.Ajv) { + // outletsRequestDynamicContextResponse_v1_schema has an optional array property called "plugins", + // which contains objects with an optional array property called "sandboxPolicies". Draft-04 does not + // support this "optional array" inside "optional array", while draft-06 and draft-07 do. + validateValidDataAgainstSchemaHelper(ajv, outletsRequestDynamicContextResponse_v1_schema, validOutletsRequestDynamicContextResponse_v1); + } + + function validateInvalidDataAgainstSchemaHelper(ajv: Ajv.Ajv, schema: object, data: any) { + const validationFunction = ajv.compile(schema); + const isValid = validationFunction(data); + expect(isValid).toBeFalse(); + } + + /** + * getItemResponse_v1_schema is an empty schema, which means that any data (valid or invalid) would pass validation against it. + * Therefore, we are not including it in the invalid data tests since it would not be meaningful. + */ + function validateInvalidDataAgainstSchemaSupporting04and06and07(ajv: Ajv.Ajv) { + validateInvalidDataAgainstSchemaHelper(ajv, authRequest_v1_schema, invalidAuthRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, authResponse_v1_schema, invalidAuthResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, getItemRequest_v1_schema, invalidGetItemRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, getItemRequest_v2_schema, invalidGetItemRequest_v2); + validateInvalidDataAgainstSchemaHelper(ajv, getItemResponse_v2_schema, invalidGetItemResponse_v2); + validateInvalidDataAgainstSchemaHelper(ajv, setItemRequest_v1_schema, invalidSetItemRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, getFeatureFlagRequest_v1_schema, invalidGetFeatureFlagRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, getFeatureFlagResponse_v1_schema, invalidGetFeatureFlagResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, setTitleRequest_v1_schema, invalidSetTitleRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, modalOpenRequest_v1_schema, invalidModalOpenRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, modalOpenRequest_v2_schema, invalidModalOpenRequest_v2); + validateInvalidDataAgainstSchemaHelper(ajv, modalCloseRequest_v1_schema, invalidModalCloseRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v1_schema, invalidGetPermissionsRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v2_schema, invalidGetPermissionsRequest_v2); + validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v3_schema, invalidGetPermissionsRequest_v3); + validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v1_schema, invalidGetPermissionsResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v2_schema, invalidGetPermissionsResponse_v2); + validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v3_schema, invalidGetPermissionsResponse_v3); + validateInvalidDataAgainstSchemaHelper(ajv, requireContextRequest_v1_schema, invalidRequireContextRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, getSettingsRequest_v1_schema, invalidGetSettingsRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, getSettingsResponse_v1_schema, invalidGetSettingsResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, setViewStateRequest_v1_schema, invalidSetViewStateRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, setViewStateResponse_v1_schema, invalidSetViewStateResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, outletsRequestContextRequest_v1_schema, invalidOutletsRequestContextRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, outletsRequestContextResponse_v1_schema, invalidOutletsRequestContextResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, outletsAddPluginRequest_v1_schema, invalidOutletsAddPluginRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, outletsRemovePluginRequest_v1_schema, invalidOutletsRemovePluginRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, outletsRequestDynamicContextRequest_v1_schema, invalidOutletsRequestDynamicContextRequest_v1); + } + + function validateInvalidDataAgainstSchemaSupportingOnly06and07(ajv: Ajv.Ajv) { + // outletsRequestDynamicContextResponse_v1_schema has an optional array property called "plugins", + // which contains objects with an optional array property called "sandboxPolicies". Draft-04 does not + // support this "optional array" inside "optional array", while draft-06 and draft-07 do. + validateInvalidDataAgainstSchemaHelper(ajv, outletsRequestDynamicContextResponse_v1_schema, invalidOutletsRequestDynamicContextResponse_v1); + } + + it('should be valid draft-04 JSON schemas', () => { + validateSchemasSupporting04and06and07(ajv04); + }); + + it('should be valid draft-06 JSON schemas', () => { + validateSchemasSupporting04and06and07(ajv06); + validateSchemasSupportingOnly06and07(ajv06); + }); + + it('should be valid draft-07 JSON schemas', () => { + validateSchemasSupporting04and06and07(ajv07); + validateSchemasSupportingOnly06and07(ajv07); + }); + + it('should validate valid data as correct - draft-04 JSON schemas', () => { + validateValidDataAgainstSchemaSupporting04and06and07(ajv04); + }); + + it('should validate valid data as correct - draft-06 JSON schemas', () => { + validateValidDataAgainstSchemaSupporting04and06and07(ajv06); + validateValidDataAgainstSchemaSupportingOnly06and07(ajv06); + }); + + it('should validate valid data as correct - draft-07 JSON schemas', () => { + validateValidDataAgainstSchemaSupporting04and06and07(ajv07); + validateValidDataAgainstSchemaSupportingOnly06and07(ajv07); + }); + + it('should validate invalid data as incorrect - draft-04 JSON schemas', () => { + validateInvalidDataAgainstSchemaSupporting04and06and07(ajv04); + }); + + it('should validate invalid data as incorrect - draft-06 JSON schemas', () => { + validateInvalidDataAgainstSchemaSupporting04and06and07(ajv06); + validateInvalidDataAgainstSchemaSupportingOnly06and07(ajv06); + }); + + it('should validate invalid data as incorrect - draft-07 JSON schemas', () => { + validateInvalidDataAgainstSchemaSupporting04and06and07(ajv07); + validateInvalidDataAgainstSchemaSupportingOnly06and07(ajv07); + }); +}); \ No newline at end of file From 2e414cb8de6c62bfa98825ef36f12cd9e3b8b41a Mon Sep 17 00:00:00 2001 From: Lukas Kistl <51904901+kistll@users.noreply.github.com> Date: Tue, 3 Mar 2026 09:44:45 +0100 Subject: [PATCH 2/4] Update comment --- karma.conf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/karma.conf.js b/karma.conf.js index f43f1b4..4bd4492 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,4 +1,4 @@ -// "resolveJsonModule": true is required to load json files in tests, e.g. meta schemas (see SchemaValidation.spec.ts) +// "resolveJsonModule": true is required to load json files in tests, e.g. meta schemas (see schemas.spec.ts) const tsconfig = { "compilerOptions": { "lib": [ From 18a6533b0c7d402d99b283751999e5cabec7e6ed Mon Sep 17 00:00:00 2001 From: Kistl Date: Tue, 3 Mar 2026 11:55:08 +0100 Subject: [PATCH 3/4] Update documentation --- docs/validation.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/validation.md b/docs/validation.md index e1ebf1a..f12f415 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -56,16 +56,11 @@ where: details describing validation error. It is up to consuming application which additional information include in this property. -**Important:** Most schemas within fsm-shell support the following JSON Schema standards: -- draft-04 +**Important:** All schemas within fsm-shell support the following JSON Schema standards [^1]: - draft-06 - draft-07 -The only current exception is outletsRequestDynamicContextResponse_v1_schema, which supports: -- draft-06 -- draft-07 - -We recommend using an external JSON Schema validation library that supports at least draft-07. Please note that future schemas may no longer support draft-04. +[^1]: Currently, most of the schemas within fsm-shell support also the JSON Schema standard draft-04. However, future schemas may no longer support draft-04. Therefore, we recommend using an external JSON Schema validation library that supports the JSON Schema standards draft-06 and/or draft-07. ## Enabling validation @@ -132,8 +127,3 @@ export class ShellPayloadValidationService implements PayloadValidator { } ``` - -**Hint:** If Ajv is use, it is recommended to use v6 as it supports the following JSON Schema standards: -- draft-04 -- draft-06 -- draft-07 From 3c5675cbcf702d1848fd8390b9fc1e22cf321f52 Mon Sep 17 00:00:00 2001 From: Kistl Date: Tue, 3 Mar 2026 15:20:19 +0100 Subject: [PATCH 4/4] Improve PR based on PR review: - Remove duplicate definition inside schema getFeatureFlagRequest_v1_schema - Add schema name to error if schema validation test fails --- .../get-feature-flag-request.v1.schema.ts | 11 +- src/validation/schemas/schemas.spec.ts | 218 ++++++++++-------- 2 files changed, 117 insertions(+), 112 deletions(-) diff --git a/src/validation/schemas/feature-flag/get-feature-flag-request.v1.schema.ts b/src/validation/schemas/feature-flag/get-feature-flag-request.v1.schema.ts index 3415d46..77787cd 100644 --- a/src/validation/schemas/feature-flag/get-feature-flag-request.v1.schema.ts +++ b/src/validation/schemas/feature-flag/get-feature-flag-request.v1.schema.ts @@ -15,16 +15,7 @@ export const getFeatureFlagRequest_v1_schema = { }, anyOf: [ { - type: 'object', - properties: { - key: { - type: 'string' - }, - defaultValue: { - type: 'boolean' - } - }, - required: ['key', 'defaultValue'] + $ref: '#/$defs/payload' }, { type: 'array', diff --git a/src/validation/schemas/schemas.spec.ts b/src/validation/schemas/schemas.spec.ts index 0de220f..26c40e2 100644 --- a/src/validation/schemas/schemas.spec.ts +++ b/src/validation/schemas/schemas.spec.ts @@ -230,63 +230,72 @@ describe('Schemas', () => { (draft4MetaSchema as any).$id = 'http://json-schema.org/draft-04/schema#'; let ajv04 = new Ajv({ meta: draft4MetaSchema }); - function validateSchemaHelper(ajv: Ajv.Ajv, schema: object) { - const result = ajv.validateSchema(schema); - // If the schema is invalid, log the errors to help with debugging - if (result === false) { - console.log(JSON.stringify(ajv.errors, null, 2)); + function validateSchemaHelper(ajv: Ajv.Ajv, schemaName: string, schema: object) { + const isValid = ajv.validateSchema(schema); + // If the schema is invalid, throw an error with the validation errors from Ajv. This will help us identify and fix any issues with the schemas. + if (isValid === false) { + const errorObject = { + schemaName, + errors: { ...ajv.errors } + }; + throw new Error(JSON.stringify(errorObject, null, 2)); + } else { + expect(isValid).toBeTrue(); } - expect(result).toBeTrue(); } function validateSchemasSupporting04and06and07(ajv: Ajv.Ajv) { - validateSchemaHelper(ajv, authRequest_v1_schema); - validateSchemaHelper(ajv, authResponse_v1_schema); - validateSchemaHelper(ajv, getItemRequest_v1_schema); - validateSchemaHelper(ajv, getItemRequest_v2_schema); - validateSchemaHelper(ajv, getItemResponse_v1_schema); - validateSchemaHelper(ajv, getItemResponse_v2_schema); - validateSchemaHelper(ajv, setItemRequest_v1_schema); - validateSchemaHelper(ajv, getFeatureFlagRequest_v1_schema); - validateSchemaHelper(ajv, getFeatureFlagResponse_v1_schema); - validateSchemaHelper(ajv, setTitleRequest_v1_schema); - validateSchemaHelper(ajv, modalOpenRequest_v1_schema); - validateSchemaHelper(ajv, modalOpenRequest_v2_schema); - validateSchemaHelper(ajv, modalCloseRequest_v1_schema); - validateSchemaHelper(ajv, getPermissionsRequest_v1_schema); - validateSchemaHelper(ajv, getPermissionsRequest_v2_schema); - validateSchemaHelper(ajv, getPermissionsRequest_v3_schema); - validateSchemaHelper(ajv, getPermissionsResponse_v1_schema); - validateSchemaHelper(ajv, getPermissionsResponse_v2_schema); - validateSchemaHelper(ajv, getPermissionsResponse_v3_schema); - validateSchemaHelper(ajv, requireContextRequest_v1_schema); - validateSchemaHelper(ajv, getSettingsRequest_v1_schema); - validateSchemaHelper(ajv, getSettingsResponse_v1_schema); - validateSchemaHelper(ajv, setViewStateRequest_v1_schema); - validateSchemaHelper(ajv, setViewStateResponse_v1_schema); - validateSchemaHelper(ajv, outletsRequestContextRequest_v1_schema); - validateSchemaHelper(ajv, outletsRequestContextResponse_v1_schema); - validateSchemaHelper(ajv, outletsAddPluginRequest_v1_schema); - validateSchemaHelper(ajv, outletsRemovePluginRequest_v1_schema); - validateSchemaHelper(ajv, outletsRequestDynamicContextRequest_v1_schema); + validateSchemaHelper(ajv, 'authRequest_v1_schema', authRequest_v1_schema); + validateSchemaHelper(ajv, 'authResponse_v1_schema', authResponse_v1_schema); + validateSchemaHelper(ajv, 'getItemRequest_v1_schema', getItemRequest_v1_schema); + validateSchemaHelper(ajv, 'getItemRequest_v2_schema', getItemRequest_v2_schema); + validateSchemaHelper(ajv, 'getItemResponse_v1_schema', getItemResponse_v1_schema); + validateSchemaHelper(ajv, 'getItemResponse_v2_schema', getItemResponse_v2_schema); + validateSchemaHelper(ajv, 'setItemRequest_v1_schema', setItemRequest_v1_schema); + validateSchemaHelper(ajv, 'getFeatureFlagRequest_v1_schema', getFeatureFlagRequest_v1_schema); + validateSchemaHelper(ajv, 'getFeatureFlagResponse_v1_schema', getFeatureFlagResponse_v1_schema); + validateSchemaHelper(ajv, 'setTitleRequest_v1_schema', setTitleRequest_v1_schema); + validateSchemaHelper(ajv, 'modalOpenRequest_v1_schema', modalOpenRequest_v1_schema); + validateSchemaHelper(ajv, 'modalOpenRequest_v2_schema', modalOpenRequest_v2_schema); + validateSchemaHelper(ajv, 'modalCloseRequest_v1_schema', modalCloseRequest_v1_schema); + validateSchemaHelper(ajv, 'getPermissionsRequest_v1_schema', getPermissionsRequest_v1_schema); + validateSchemaHelper(ajv, 'getPermissionsRequest_v2_schema', getPermissionsRequest_v2_schema); + validateSchemaHelper(ajv, 'getPermissionsRequest_v3_schema', getPermissionsRequest_v3_schema); + validateSchemaHelper(ajv, 'getPermissionsResponse_v1_schema', getPermissionsResponse_v1_schema); + validateSchemaHelper(ajv, 'getPermissionsResponse_v2_schema', getPermissionsResponse_v2_schema); + validateSchemaHelper(ajv, 'getPermissionsResponse_v3_schema', getPermissionsResponse_v3_schema); + validateSchemaHelper(ajv, 'requireContextRequest_v1_schema', requireContextRequest_v1_schema); + validateSchemaHelper(ajv, 'getSettingsRequest_v1_schema', getSettingsRequest_v1_schema); + validateSchemaHelper(ajv, 'getSettingsResponse_v1_schema', getSettingsResponse_v1_schema); + validateSchemaHelper(ajv, 'setViewStateRequest_v1_schema', setViewStateRequest_v1_schema); + validateSchemaHelper(ajv, 'setViewStateResponse_v1_schema', setViewStateResponse_v1_schema); + validateSchemaHelper(ajv, 'outletsRequestContextRequest_v1_schema', outletsRequestContextRequest_v1_schema); + validateSchemaHelper(ajv, 'outletsRequestContextResponse_v1_schema', outletsRequestContextResponse_v1_schema); + validateSchemaHelper(ajv, 'outletsAddPluginRequest_v1_schema', outletsAddPluginRequest_v1_schema); + validateSchemaHelper(ajv, 'outletsRemovePluginRequest_v1_schema', outletsRemovePluginRequest_v1_schema); + validateSchemaHelper(ajv, 'outletsRequestDynamicContextRequest_v1_schema', outletsRequestDynamicContextRequest_v1_schema); } function validateSchemasSupportingOnly06and07(ajv: Ajv.Ajv) { // outletsRequestDynamicContextResponse_v1_schema has an optional array property called "plugins", // which contains objects with an optional array property called "sandboxPolicies". Draft-04 does not // support this "optional array" inside "optional array", while draft-06 and draft-07 do. - validateSchemaHelper(ajv, outletsRequestDynamicContextResponse_v1_schema); + validateSchemaHelper(ajv, 'outletsRequestDynamicContextResponse_v1_schema', outletsRequestDynamicContextResponse_v1_schema); } - function validateValidDataAgainstSchemaHelper(ajv: Ajv.Ajv, schema: object, data: any) { + function validateValidDataAgainstSchemaHelper(ajv: Ajv.Ajv, schemaName: string, schema: object, data: any) { const validationFunction = ajv.compile(schema); const isValid = validationFunction(data); - + // If the data is invalid, throw an error with the validation errors from Ajv. This will help us identify and fix any issues with the schemas or the test data. if (isValid === false) { - console.log(JSON.stringify(validationFunction.errors, null, 2)); + const errorObject = { + schemaName, + errors: { ...validationFunction.errors } + }; + throw new Error(JSON.stringify(errorObject, null, 2)); + } else { + expect(isValid).toBeTrue(); } - - expect(isValid).toBeTrue(); } /** @@ -294,47 +303,52 @@ describe('Schemas', () => { * Therefore, we are not including it in the valid data tests since it would not be meaningful. */ function validateValidDataAgainstSchemaSupporting04and06and07(ajv: Ajv.Ajv) { - validateValidDataAgainstSchemaHelper(ajv, authRequest_v1_schema, validAuthRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, authResponse_v1_schema, validAuthResponse_v1); - validateValidDataAgainstSchemaHelper(ajv, getItemRequest_v1_schema, validGetItemRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, getItemRequest_v2_schema, validGetItemRequest_v2); - validateValidDataAgainstSchemaHelper(ajv, getItemResponse_v2_schema, validGetItemResponse_v2); - validateValidDataAgainstSchemaHelper(ajv, setItemRequest_v1_schema, validSetItemRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, getFeatureFlagRequest_v1_schema, validGetFeatureFlagRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, getFeatureFlagResponse_v1_schema, validGetFeatureFlagResponse_v1); - validateValidDataAgainstSchemaHelper(ajv, setTitleRequest_v1_schema, validSetTitleRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, modalOpenRequest_v1_schema, validModalOpenRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, modalOpenRequest_v2_schema, validModalOpenRequest_v2); - validateValidDataAgainstSchemaHelper(ajv, modalCloseRequest_v1_schema, validModalCloseRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v1_schema, validGetPermissionsRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v2_schema, validGetPermissionsRequest_v2); - validateValidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v3_schema, validGetPermissionsRequest_v3); - validateValidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v1_schema, validGetPermissionsResponse_v1); - validateValidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v2_schema, validGetPermissionsResponse_v2); - validateValidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v3_schema, validGetPermissionsResponse_v3); - validateValidDataAgainstSchemaHelper(ajv, requireContextRequest_v1_schema, validRequireContextRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, getSettingsRequest_v1_schema, validGetSettingsRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, getSettingsResponse_v1_schema, validGetSettingsResponse_v1); - validateValidDataAgainstSchemaHelper(ajv, setViewStateRequest_v1_schema, validSetViewStateRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, setViewStateResponse_v1_schema, validSetViewStateResponse_v1); - validateValidDataAgainstSchemaHelper(ajv, outletsRequestContextRequest_v1_schema, validOutletsRequestContextRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, outletsRequestContextResponse_v1_schema, validOutletsRequestContextResponse_v1); - validateValidDataAgainstSchemaHelper(ajv, outletsAddPluginRequest_v1_schema, validOutletsAddPluginRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, outletsRemovePluginRequest_v1_schema, validOutletsRemovePluginRequest_v1); - validateValidDataAgainstSchemaHelper(ajv, outletsRequestDynamicContextRequest_v1_schema, validOutletsRequestDynamicContextRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'authRequest_v1_schema', authRequest_v1_schema, validAuthRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'authResponse_v1_schema', authResponse_v1_schema, validAuthResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, 'getItemRequest_v1_schema', getItemRequest_v1_schema, validGetItemRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'getItemRequest_v2_schema', getItemRequest_v2_schema, validGetItemRequest_v2); + validateValidDataAgainstSchemaHelper(ajv, 'getItemResponse_v2_schema', getItemResponse_v2_schema, validGetItemResponse_v2); + validateValidDataAgainstSchemaHelper(ajv, 'setItemRequest_v1_schema', setItemRequest_v1_schema, validSetItemRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'getFeatureFlagRequest_v1_schema', getFeatureFlagRequest_v1_schema, validGetFeatureFlagRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'getFeatureFlagResponse_v1_schema', getFeatureFlagResponse_v1_schema, validGetFeatureFlagResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, 'setTitleRequest_v1_schema', setTitleRequest_v1_schema, validSetTitleRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'modalOpenRequest_v1_schema', modalOpenRequest_v1_schema, validModalOpenRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'modalOpenRequest_v2_schema', modalOpenRequest_v2_schema, validModalOpenRequest_v2); + validateValidDataAgainstSchemaHelper(ajv, 'modalCloseRequest_v1_schema', modalCloseRequest_v1_schema, validModalCloseRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'getPermissionsRequest_v1_schema', getPermissionsRequest_v1_schema, validGetPermissionsRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'getPermissionsRequest_v2_schema', getPermissionsRequest_v2_schema, validGetPermissionsRequest_v2); + validateValidDataAgainstSchemaHelper(ajv, 'getPermissionsRequest_v3_schema', getPermissionsRequest_v3_schema, validGetPermissionsRequest_v3); + validateValidDataAgainstSchemaHelper(ajv, 'getPermissionsResponse_v1_schema', getPermissionsResponse_v1_schema, validGetPermissionsResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, 'getPermissionsResponse_v2_schema', getPermissionsResponse_v2_schema, validGetPermissionsResponse_v2); + validateValidDataAgainstSchemaHelper(ajv, 'getPermissionsResponse_v3_schema', getPermissionsResponse_v3_schema, validGetPermissionsResponse_v3); + validateValidDataAgainstSchemaHelper(ajv, 'requireContextRequest_v1_schema', requireContextRequest_v1_schema, validRequireContextRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'getSettingsRequest_v1_schema', getSettingsRequest_v1_schema, validGetSettingsRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'getSettingsResponse_v1_schema', getSettingsResponse_v1_schema, validGetSettingsResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, 'setViewStateRequest_v1_schema', setViewStateRequest_v1_schema, validSetViewStateRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'setViewStateResponse_v1_schema', setViewStateResponse_v1_schema, validSetViewStateResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, 'outletsRequestContextRequest_v1_schema', outletsRequestContextRequest_v1_schema, validOutletsRequestContextRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'outletsRequestContextResponse_v1_schema', outletsRequestContextResponse_v1_schema, validOutletsRequestContextResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, 'outletsAddPluginRequest_v1_schema', outletsAddPluginRequest_v1_schema, validOutletsAddPluginRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'outletsRemovePluginRequest_v1_schema', outletsRemovePluginRequest_v1_schema, validOutletsRemovePluginRequest_v1); + validateValidDataAgainstSchemaHelper(ajv, 'outletsRequestDynamicContextRequest_v1_schema', outletsRequestDynamicContextRequest_v1_schema, validOutletsRequestDynamicContextRequest_v1); } function validateValidDataAgainstSchemaSupportingOnly06and07(ajv: Ajv.Ajv) { // outletsRequestDynamicContextResponse_v1_schema has an optional array property called "plugins", // which contains objects with an optional array property called "sandboxPolicies". Draft-04 does not // support this "optional array" inside "optional array", while draft-06 and draft-07 do. - validateValidDataAgainstSchemaHelper(ajv, outletsRequestDynamicContextResponse_v1_schema, validOutletsRequestDynamicContextResponse_v1); + validateValidDataAgainstSchemaHelper(ajv, 'outletsRequestDynamicContextResponse_v1_schema', outletsRequestDynamicContextResponse_v1_schema, validOutletsRequestDynamicContextResponse_v1); } - function validateInvalidDataAgainstSchemaHelper(ajv: Ajv.Ajv, schema: object, data: any) { + function validateInvalidDataAgainstSchemaHelper(ajv: Ajv.Ajv, schemaName: string, schema: object, data: any) { const validationFunction = ajv.compile(schema); - const isValid = validationFunction(data); - expect(isValid).toBeFalse(); + const isValid = validationFunction(data); + // If the data is valid even if it is expected to be invalid, throw an error with the schema name to indicate the test failure. + if (isValid === true) { + throw new Error(`Expected validation to fail for schema: ${schemaName}`); + } else { + expect(isValid).toBeFalse(); + } } /** @@ -342,41 +356,41 @@ describe('Schemas', () => { * Therefore, we are not including it in the invalid data tests since it would not be meaningful. */ function validateInvalidDataAgainstSchemaSupporting04and06and07(ajv: Ajv.Ajv) { - validateInvalidDataAgainstSchemaHelper(ajv, authRequest_v1_schema, invalidAuthRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, authResponse_v1_schema, invalidAuthResponse_v1); - validateInvalidDataAgainstSchemaHelper(ajv, getItemRequest_v1_schema, invalidGetItemRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, getItemRequest_v2_schema, invalidGetItemRequest_v2); - validateInvalidDataAgainstSchemaHelper(ajv, getItemResponse_v2_schema, invalidGetItemResponse_v2); - validateInvalidDataAgainstSchemaHelper(ajv, setItemRequest_v1_schema, invalidSetItemRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, getFeatureFlagRequest_v1_schema, invalidGetFeatureFlagRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, getFeatureFlagResponse_v1_schema, invalidGetFeatureFlagResponse_v1); - validateInvalidDataAgainstSchemaHelper(ajv, setTitleRequest_v1_schema, invalidSetTitleRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, modalOpenRequest_v1_schema, invalidModalOpenRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, modalOpenRequest_v2_schema, invalidModalOpenRequest_v2); - validateInvalidDataAgainstSchemaHelper(ajv, modalCloseRequest_v1_schema, invalidModalCloseRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v1_schema, invalidGetPermissionsRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v2_schema, invalidGetPermissionsRequest_v2); - validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsRequest_v3_schema, invalidGetPermissionsRequest_v3); - validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v1_schema, invalidGetPermissionsResponse_v1); - validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v2_schema, invalidGetPermissionsResponse_v2); - validateInvalidDataAgainstSchemaHelper(ajv, getPermissionsResponse_v3_schema, invalidGetPermissionsResponse_v3); - validateInvalidDataAgainstSchemaHelper(ajv, requireContextRequest_v1_schema, invalidRequireContextRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, getSettingsRequest_v1_schema, invalidGetSettingsRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, getSettingsResponse_v1_schema, invalidGetSettingsResponse_v1); - validateInvalidDataAgainstSchemaHelper(ajv, setViewStateRequest_v1_schema, invalidSetViewStateRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, setViewStateResponse_v1_schema, invalidSetViewStateResponse_v1); - validateInvalidDataAgainstSchemaHelper(ajv, outletsRequestContextRequest_v1_schema, invalidOutletsRequestContextRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, outletsRequestContextResponse_v1_schema, invalidOutletsRequestContextResponse_v1); - validateInvalidDataAgainstSchemaHelper(ajv, outletsAddPluginRequest_v1_schema, invalidOutletsAddPluginRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, outletsRemovePluginRequest_v1_schema, invalidOutletsRemovePluginRequest_v1); - validateInvalidDataAgainstSchemaHelper(ajv, outletsRequestDynamicContextRequest_v1_schema, invalidOutletsRequestDynamicContextRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'authRequest_v1_schema', authRequest_v1_schema, invalidAuthRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'authResponse_v1_schema', authResponse_v1_schema, invalidAuthResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'getItemRequest_v1_schema', getItemRequest_v1_schema, invalidGetItemRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'getItemRequest_v2_schema', getItemRequest_v2_schema, invalidGetItemRequest_v2); + validateInvalidDataAgainstSchemaHelper(ajv, 'getItemResponse_v2_schema', getItemResponse_v2_schema, invalidGetItemResponse_v2); + validateInvalidDataAgainstSchemaHelper(ajv, 'setItemRequest_v1_schema', setItemRequest_v1_schema, invalidSetItemRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'getFeatureFlagRequest_v1_schema', getFeatureFlagRequest_v1_schema, invalidGetFeatureFlagRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'getFeatureFlagResponse_v1_schema', getFeatureFlagResponse_v1_schema, invalidGetFeatureFlagResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'setTitleRequest_v1_schema', setTitleRequest_v1_schema, invalidSetTitleRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'modalOpenRequest_v1_schema', modalOpenRequest_v1_schema, invalidModalOpenRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'modalOpenRequest_v2_schema', modalOpenRequest_v2_schema, invalidModalOpenRequest_v2); + validateInvalidDataAgainstSchemaHelper(ajv, 'modalCloseRequest_v1_schema', modalCloseRequest_v1_schema, invalidModalCloseRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'getPermissionsRequest_v1_schema', getPermissionsRequest_v1_schema, invalidGetPermissionsRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'getPermissionsRequest_v2_schema', getPermissionsRequest_v2_schema, invalidGetPermissionsRequest_v2); + validateInvalidDataAgainstSchemaHelper(ajv, 'getPermissionsRequest_v3_schema', getPermissionsRequest_v3_schema, invalidGetPermissionsRequest_v3); + validateInvalidDataAgainstSchemaHelper(ajv, 'getPermissionsResponse_v1_schema', getPermissionsResponse_v1_schema, invalidGetPermissionsResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'getPermissionsResponse_v2_schema', getPermissionsResponse_v2_schema, invalidGetPermissionsResponse_v2); + validateInvalidDataAgainstSchemaHelper(ajv, 'getPermissionsResponse_v3_schema', getPermissionsResponse_v3_schema, invalidGetPermissionsResponse_v3); + validateInvalidDataAgainstSchemaHelper(ajv, 'requireContextRequest_v1_schema', requireContextRequest_v1_schema, invalidRequireContextRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'getSettingsRequest_v1_schema', getSettingsRequest_v1_schema, invalidGetSettingsRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'getSettingsResponse_v1_schema', getSettingsResponse_v1_schema, invalidGetSettingsResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'setViewStateRequest_v1_schema', setViewStateRequest_v1_schema, invalidSetViewStateRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'setViewStateResponse_v1_schema', setViewStateResponse_v1_schema, invalidSetViewStateResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'outletsRequestContextRequest_v1_schema', outletsRequestContextRequest_v1_schema, invalidOutletsRequestContextRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'outletsRequestContextResponse_v1_schema', outletsRequestContextResponse_v1_schema, invalidOutletsRequestContextResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'outletsAddPluginRequest_v1_schema', outletsAddPluginRequest_v1_schema, invalidOutletsAddPluginRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'outletsRemovePluginRequest_v1_schema', outletsRemovePluginRequest_v1_schema, invalidOutletsRemovePluginRequest_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'outletsRequestDynamicContextRequest_v1_schema', outletsRequestDynamicContextRequest_v1_schema, invalidOutletsRequestDynamicContextRequest_v1); } function validateInvalidDataAgainstSchemaSupportingOnly06and07(ajv: Ajv.Ajv) { // outletsRequestDynamicContextResponse_v1_schema has an optional array property called "plugins", // which contains objects with an optional array property called "sandboxPolicies". Draft-04 does not // support this "optional array" inside "optional array", while draft-06 and draft-07 do. - validateInvalidDataAgainstSchemaHelper(ajv, outletsRequestDynamicContextResponse_v1_schema, invalidOutletsRequestDynamicContextResponse_v1); + validateInvalidDataAgainstSchemaHelper(ajv, 'outletsRequestDynamicContextResponse_v1_schema', outletsRequestDynamicContextResponse_v1_schema, invalidOutletsRequestDynamicContextResponse_v1); } it('should be valid draft-04 JSON schemas', () => {