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..f12f415 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -56,6 +56,12 @@ where: details describing validation error. It is up to consuming application which additional information include in this property. +**Important:** All schemas within fsm-shell support the following JSON Schema standards [^1]: +- draft-06 +- draft-07 + +[^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 To enable validation the consumer of fsm-shell library should call `setValidator` method on ShellSdk instance. diff --git a/karma.conf.js b/karma.conf.js index 045552b..4bd4492 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 schemas.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..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 @@ -1,12 +1,27 @@ 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: [ + { + $ref: '#/$defs/payload' + }, + { + 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..26c40e2 --- /dev/null +++ b/src/validation/schemas/schemas.spec.ts @@ -0,0 +1,437 @@ +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, 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(); + } + } + + function validateSchemasSupporting04and06and07(ajv: Ajv.Ajv) { + 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', outletsRequestDynamicContextResponse_v1_schema); + } + + 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) { + const errorObject = { + schemaName, + errors: { ...validationFunction.errors } + }; + throw new Error(JSON.stringify(errorObject, null, 2)); + } else { + 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', 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', outletsRequestDynamicContextResponse_v1_schema, validOutletsRequestDynamicContextResponse_v1); + } + + function validateInvalidDataAgainstSchemaHelper(ajv: Ajv.Ajv, schemaName: string, schema: object, data: any) { + const validationFunction = ajv.compile(schema); + 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(); + } + } + + /** + * 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', 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', 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