diff --git a/README.md b/README.md index 691059a3..d02b80a6 100644 --- a/README.md +++ b/README.md @@ -13,23 +13,26 @@ It has the following features - ## Table of Contents -- [Brief overview](#brief-overview) -- [Setup](#setup) - - [FCA URL deployed and available in the S3](#fca-url-deployed-and-available-in-the-s3) - - [Below are the steps to run FCA in local system](#below-are-the-steps-to-run-fca-in-local-system) -- [Supported ways of Execution](#supported-ways-of-execution) -- [Supported targets](#supported-targets) -- [Supported Modes of execution](#supported-modes-of-execution) -- [Supported validations](#supported-validations) -- [Supported ways of retrieving reports](#supported-ways-of-retrieving-reports) -- [Supported Report Parameters](#supported-report-parameters) -- [PR and merge process](#pr-and-merge-process) -- [Supported URL parameters](#supported-url-parameters) -- [Supported PubSub Handlers](#supported-pubsub-handlers) -- [Plugins](#plugins) -- [Connect to mock Firebolt OS](#connect-to-mock-firebolt-os) - - [Timeout in UI prompt](#timeout-in-ui-prompt) - +- [firebolt-certification-app](#firebolt-certification-app) + - [Brief overview](#brief-overview) + - [Table of Contents](#table-of-contents) + - [Setup](#setup) + - [FCA URL deployed and available in the S3](#fca-url-deployed-and-available-in-the-s3) + - [Below are the steps to run FCA in local system](#below-are-the-steps-to-run-fca-in-local-system) + - [Supported ways of Execution](#supported-ways-of-execution) + - [Supported targets](#supported-targets) + - [Supported Modes of execution](#supported-modes-of-execution) + - [Supported validations](#supported-validations) + - [Supported ways of retrieving reports](#supported-ways-of-retrieving-reports) + - [Supported Report Parameters](#supported-report-parameters) + - [PR and merge process](#pr-and-merge-process) + - [Supported URL parameters](#supported-url-parameters) + - [Supported Intent Parameters](#supported-intent-parameters) + - [Supported PubSub Handlers](#supported-pubsub-handlers) + - [Plugins](#plugins) + - [Connect to mock Firebolt OS](#connect-to-mock-firebolt-os) + - [Timeout in UI prompt](#timeout-in-ui-prompt) + ## Setup Use a recent version of node. At the time of writing, Node 14.15.x was LTS. An `.nvmrc` file is included for those using a node version manager. Everyone else, swim at your own risk. @@ -112,7 +115,33 @@ Mode of execution implies the way in which an API is invoked. There are 2 modes - If FCA systemui=true, FCA acts as the base app in case of ripple. The background color will be changed to purple and it will display one more button as "Launch FCA app" to launch FCA as third-party app on Ripple devices. - TestContext: testContext=true - If testContext=true, it will add the field context in mocha report generated - +- AppId: appId=`` + - `appId` used to launch the app. +- Mac Address: macAddress=`` + - `macAddress` of the device running the tests. +- appType: appType=`` + - `appType` is the type of app being launched. +- Pub Sub Subscribe suffix : pubSubSubscribeSuffix=`` + - `pubSubSubscribeSuffix` is the subscribe suffix value used for Pub Sub communication. +- Pub Sub Publish suffix : pubSubPublishSuffix=`` + - `pubSubPublishSuffix` is the publish suffix value used for Pub Sub communication. + +## Supported Intent Parameters +- appType: + - Classifier for the app - Launch the certification app for certification validations. Launching a firebolt app for app certification. +- appId: + - When `appId` is specified in the intent, it will be used to launch the app. +- macAddress: + - When `macAddress` is specified in the intent, it indicates the mac address of the device running the tests. +- PubSub Publish Suffix: + - When `pubSubPublishSuffix` is specified in the intent, it publishes to the topic. +- PubSub Subscribe Suffix: + - When `pubSubSubscribeSuffix` is specified in the intent, it subscribes to the topic. +- pubSubUrl: + - Sets the the url to use for a PubSub server. +- registerprovider: + - When `registerProvider = false`, then certification app will not register for userInterest provider. + ## Supported PubSub Handlers The code for handling different types of PubSub requests is located in `./src/pubsub/handlers`. Below are the supported handlers for various types of PubSub requests. diff --git a/docs/images/generateAPIValidaionResult.png b/docs/images/generateAPIValidaionResult.png new file mode 100644 index 00000000..e90874fc Binary files /dev/null and b/docs/images/generateAPIValidaionResult.png differ diff --git a/docs/images/testRunnerFlow.png b/docs/images/testRunnerFlow.png new file mode 100644 index 00000000..fb7fa77d Binary files /dev/null and b/docs/images/testRunnerFlow.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..29084e0b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,7 @@ +# Sanity Suite Flow chart + +## Implementation +![Implementation Diagram](./images/testRunnerFlow.png) + +## Generating Report Logs +![Manual Trigger Diagram](./images/generateAPIValidaionResult.png) diff --git a/package.json b/package.json index de7e9153..e7a746c0 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "version": "1.0.0", + "version": "0.10.0", "name": "firebolt-certification", "description": "Reference App to demonstrate Firebolt APIs and Lifecycle", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.9", - "@firebolt-js/manage-sdk": "1.0.0", - "@firebolt-js/sdk": "1.0.0", - "@lightningjs/core": "*", + "@firebolt-js/manage-sdk": "0.1.0-next.1", + "@firebolt-js/sdk": "0.10.0", + "@lightningjs/core": "2.11.0", "@lightningjs/sdk": "^5.0.1", "@lightningjs/ui-components": "^2.2.2", "dotenv": "^14.2.0", @@ -16,11 +16,11 @@ "glob-parent": "^5.1.2", "json-schema-ref-parser": "^9.0.9", "jsonschema": "^1.4.0", - "lzutf8": "0.6.0", "perf_hooks": "^0.0.1", "rxjs": "7.2.0", "uuid": "^9.0.0", - "winston": "^3.8.2", + "winston": "3.15.0", + "winston-transport": "4.7.1", "xml2js": "^0.5.0" }, "scripts": { diff --git a/src/App.js b/src/App.js index a6c621cc..741d46b0 100644 --- a/src/App.js +++ b/src/App.js @@ -32,12 +32,11 @@ import PinChallengeProviderDelegater from './providers/PinChallengeDelegater'; import KeyboardProviderDelegater from './providers/KeyboardProviderDelegater'; import AckChallengeProviderDelegater from './providers/AckChallengeDelegater'; const logger = require('./utils/Logger')('App.js'); -import FireboltTransportInvoker from './FireboltTransportInvoker'; import { handleAsyncFunction } from './utils/Utils'; import { withAnnouncer } from '@lightningjs/ui-components'; const Base = withAnnouncer(lng.Application); import Toast, { eventEmitter } from './Toast'; -import IntentReader from './IntentReader'; +import IntentReader from 'IntentReader'; export default class App extends Base { static _template() { @@ -100,8 +99,8 @@ export default class App extends Base { const testContext = new URLSearchParams(window.location.search).get('testContext'); const reportingId = new URLSearchParams(appUrl.search).get('reportingId'); const standalone = new URLSearchParams(appUrl.search).get('standalone'); + const standalonePrefix = new URLSearchParams(appUrl.search).get('standalonePrefix'); this.systemui = new URLSearchParams(window.location.search).get('systemui'); - this.testToken = new URLSearchParams(window.location.search).get('testtoken'); this.pubSubUuidPresent = false; this.appContinue = false; process.env.LIFECYCLE_VALIDATION = lifecycle; @@ -109,9 +108,19 @@ export default class App extends Base { process.env.MF_VALUE = false; testContext ? (process.env.TESTCONTEXT = JSON.parse(testContext)) : (process.env.TESTCONTEXT = false); process.env.TESTCONTEXT = true; // Making TESTCONTEXT = true by default. This line will be removed in later stages when required - process.env.TEST_TOKEN = this.testToken; process.env.REPORTINGID = reportingId; process.env.STANDALONE = standalone; + process.env.STANDALONE_PREFIX = standalonePrefix; + process.env.ID = 0; + process.env.MACADDRESS = new URLSearchParams(appUrl.search).get('macaddress'); + process.env.CURRENT_APPID = new URLSearchParams(appUrl.search).get('appId'); + process.env.APP_TYPE = new URLSearchParams(appUrl.search).get('appType'); + process.env.PUB_SUB_URL = new URLSearchParams(appUrl.search).get('pubSubUrl'); + process.env.PUB_SUB_TOKEN = new URLSearchParams(appUrl.search).get('pubSubToken'); + process.env.PUBSUB_SUBSCRIBE_TOPIC_SUFFIX = new URLSearchParams(appUrl.search).get('pubSubSubscribeSuffix'); + process.env.PUBSUB_PUBLISH_TOPIC_SUFFIX = new URLSearchParams(appUrl.search).get('pubSubPublishSuffix'); + process.env.SDKS_AVAILABLE = [...CONSTANTS.defaultSDKs, ...CONSTANTS.additionalSDKs]; + process.env.REGION = new URLSearchParams(appUrl.search).get('region'); if (platform) { process.env.PLATFORM = platform; } else { @@ -149,7 +158,6 @@ export default class App extends Base { this.pubSubListener(); } getCurrentAppID().then((res) => { - process.env.APPID = res; this._setState('LoadingState'); }); } @@ -377,6 +385,13 @@ export default class App extends Base { if (lifecycle_validationString == true) { process.env.LIFECYCLE_VALIDATION = 'true'; } + if (query.params.pubSubPublishSuffix) { + process.env.PUBSUB_PUBLISH_TOPIC_SUFFIX = query.params.pubSubPublishSuffix; + } + + if (query.params.pubSubSubscribeSuffix) { + process.env.PUBSUB_SUBSCRIBE_TOPIC_SUFFIX = query.params.pubSubSubscribeSuffix; + } process.env.APP_TYPE = query.params.appType ? query.params.appType.toLowerCase() : CONSTANTS.FIREBOLT_CONST; @@ -393,18 +408,26 @@ export default class App extends Base { console.log('Error getting App Id :: ', err); } - if (query.params.testtoken) { - process.env.TEST_TOKEN = query.params.testtoken; - } else { - logger.error('No Test Token Found in Parameter Initialization response...', 'getParameterInitializationValues'); - } - if (query.params.macaddress) { process.env.MACADDRESS = query.params.macaddress; } else { logger.error('No Mac Address Found in Parameter Initialization response...', 'getParameterInitializationValues'); } + // Set the pubSub URL if present + if (query.params.pubSubUrl) { + process.env.PUB_SUB_URL = query.params.pubSubUrl; + } + // Set the pubSub token if present + if (query.params.pubSubToken) { + process.env.PUB_SUB_TOKEN = query.params.pubSubToken; + } + + // Set the region if present + if (query.params.region) { + process.env.REGION = query.params.region; + } + if (query.task) { setTimeout(() => { const intentReader = new IntentReader(); diff --git a/src/EventInvocation.js b/src/EventInvocation.js index e08429ed..f7b65c5a 100644 --- a/src/EventInvocation.js +++ b/src/EventInvocation.js @@ -23,7 +23,7 @@ // * Return results of all the events // ************* End Description ********** -import { dereferenceOpenRPC, errorSchemaCheck, rpcEventHandler } from './utils/Utils'; +import { dereferenceOpenRPC, errorSchemaCheck } from './utils/Utils'; import { MODULE_MAP } from './FireboltExampleInvoker'; import { CONSTANTS } from './constant'; import Transport from '@firebolt-js/sdk/dist/lib/Transport/index.mjs'; @@ -39,7 +39,9 @@ class EventHandler { this.moduleWithEventName = moduleWithEventName; const event = moduleWithEventName.split('.')[1]; this.eventName = this.parseEventName(event); - this.eventSchema = this.getSchema(moduleWithEventName, schemaList); + if (process.env.STANDALONE == true) { + this.eventSchema = this.getSchema(moduleWithEventName, schemaList); + } this.initializationTime = new Date(); this.eventListener = null; } @@ -69,14 +71,24 @@ class EventHandler { } // Handle, parse and store the resolved event data from listener handleEvent(eventData) { - const eventSchemaResponse = this.eventSchemaValidation(eventData); - const eventDataObject = { - eventName: this.eventName, - eventListenerId: this.eventListener.eventListenerId, - eventResponse: eventData, - eventSchemaResult: eventSchemaResponse, - eventTime: new Date(), - }; + let eventDataObject; + if (process.env.STANDALONE == true) { + const eventSchemaResponse = this.eventSchemaValidation(eventData); + eventDataObject = { + eventName: this.eventName, + eventListenerId: this.eventListener.eventListenerId, + eventResponse: eventData, + eventSchemaResult: eventSchemaResponse, + eventTime: new Date(), + }; + } else { + eventDataObject = { + eventName: this.eventName, + eventListenerId: this.eventListener.id, + eventResponse: eventData, + eventTime: new Date(), + }; + } eventHistory.push(eventDataObject); } // Schema validation for resolved event data @@ -107,20 +119,20 @@ class EventHandler { export class EventInvocation { // This method accepts the message params and return the listener response and schema response async northBoundEventHandling(message) { - let responseCode; const eventParams = message.params; const moduleWithEventName = eventParams.event; const params = eventParams.params; const [listenerResponse, uniqueListenerKey] = await this.registerEvent(moduleWithEventName, params); const registrationResponse = {}; - registrationResponse['eventName'] = moduleWithEventName; - registrationResponse['eventListenerId'] = uniqueListenerKey; - if (listenerResponse && Number.isInteger(listenerResponse) && listenerResponse > 0) { - registrationResponse['eventListenerResponse'] = { - listenerResponse: listenerResponse, - error: null, - }; + if (process.env.STANDALONE == true) { + registrationResponse['eventName'] = moduleWithEventName; + registrationResponse['eventListenerId'] = uniqueListenerKey; + if (listenerResponse && Number.isInteger(listenerResponse) && listenerResponse > 0) { + registrationResponse['eventListenerResponse'] = { + listenerResponse: listenerResponse, + error: null, + }; // Handling not supported api to check error schema if it gives a valid response let schemaValidationResult = {}; let schemaValidationStatus = CONSTANTS.PASS; @@ -128,32 +140,47 @@ export class EventInvocation { schemaValidationResult = errorSchemaCheck(listenerResponse); schemaValidationStatus = CONSTANTS.FAIL; } - registrationResponse['eventListenerSchemaResult'] = { - status: schemaValidationStatus, - eventSchemaResult: schemaValidationResult, - }; - eventHandlerMap.get(uniqueListenerKey).setEventListener(registrationResponse); - } else { - if (CONSTANTS.ERROR_LIST.includes(listenerResponse.message)) { - responseCode = CONSTANTS.STATUS_CODE[3]; - registrationResponse['responseCode'] = responseCode; - } - registrationResponse['eventListenerResponse'] = { result: null, error: listenerResponse }; - // In case of error, validate error against errorschema - const schemaValidationResult = errorSchemaCheck(listenerResponse); - if (schemaValidationResult && schemaValidationResult.errors && schemaValidationResult.errors.length > 0) { registrationResponse['eventListenerSchemaResult'] = { - status: CONSTANTS.FAIL, - eventSchemaResult: {}, + status: schemaValidationStatus, + eventSchemaResult: schemaValidationResult, }; + eventHandlerMap.get(uniqueListenerKey).setEventListener(registrationResponse); } else { - registrationResponse['eventListenerSchemaResult'] = { - status: CONSTANTS.PASS, - eventSchemaResult: schemaValidationResult, + if (CONSTANTS.ERROR_LIST.includes(listenerResponse.message)) { + const responseCode = CONSTANTS.STATUS_CODE[3]; + registrationResponse['responseCode'] = responseCode; + } + registrationResponse['eventListenerResponse'] = { result: null, error: listenerResponse }; + // In case of error, validate error against errorschema + const schemaValidationResult = errorSchemaCheck(listenerResponse); + if (schemaValidationResult && schemaValidationResult.errors && schemaValidationResult.errors.length > 0) { + registrationResponse['eventListenerSchemaResult'] = { + status: CONSTANTS.FAIL, + eventSchemaResult: {}, + }; + } else { + registrationResponse['eventListenerSchemaResult'] = { + status: CONSTANTS.PASS, + eventSchemaResult: schemaValidationResult, + }; + } + } + return registrationResponse; + } else { + registrationResponse['jsonrpc'] = '2.0'; + registrationResponse['id'] = null; + if (listenerResponse && Number.isInteger(listenerResponse) && listenerResponse > 0) { + registrationResponse['id'] = listenerResponse; + registrationResponse['result'] = { + listening: true, + event: moduleWithEventName, }; + eventHandlerMap.get(uniqueListenerKey).setEventListener(registrationResponse); + } else { + registrationResponse['error'] = listenerResponse; } + return registrationResponse; } - return registrationResponse; } // This method will listen to event and capture the event response after triggering @@ -298,8 +325,13 @@ export class EventInvocation { // Return the event response object for the eventName passed as the param getEventResponse(message) { try { + let filteredEventDataObjectList; const eventName = message.params.event; - const filteredEventDataObjectList = eventHistory.filter((element) => element.eventListenerId == eventName); + if (process.env.STANDALONE == true) { + filteredEventDataObjectList = eventHistory.filter((element) => element.eventListenerId == eventName); + } else { + filteredEventDataObjectList = eventHistory.filter((element) => element.eventListenerId.toString() == eventName.split('-').pop()); + } if (filteredEventDataObjectList.length) { const eventDataObject = filteredEventDataObjectList[filteredEventDataObjectList.length - 1]; return eventDataObject; diff --git a/src/FireboltExampleInvoker.js b/src/FireboltExampleInvoker.js index 903f19ff..785d589b 100644 --- a/src/FireboltExampleInvoker.js +++ b/src/FireboltExampleInvoker.js @@ -20,7 +20,6 @@ import { Accessibility, Account, Advertising, Authentication, Capabilities, Devi import { Accessory, Advertising as ManageAdvertising, - Power, AcknowledgeChallenge, Device as ManageDevice, Wifi, @@ -32,10 +31,9 @@ import { Privacy, VoiceGuidance, UserGrants, - Metrics as ManageMetrics, - SecureStorage as ManageSecureStorage, - Discovery as ManageDiscovery, AudioDescriptions, + LifecycleManagement, + DeveloperTools, } from '@firebolt-js/manage-sdk'; import DiscoveryInvoker from './invokers/DiscoveryInvoker'; const discoveryInvoker = new DiscoveryInvoker(); @@ -71,7 +69,6 @@ const CORE_MODULE_MAP = { }; const MANAGE_MODULE_MAP = { - power: Power, accessory: Accessory, advertising: ManageAdvertising, acknowledgechallenge: AcknowledgeChallenge, @@ -85,10 +82,9 @@ const MANAGE_MODULE_MAP = { voiceguidance: VoiceGuidance, localization: ManageLocalization, usergrants: UserGrants, - metrics: ManageMetrics, - securestorage: ManageSecureStorage, - discovery: ManageDiscovery, audiodescriptions: AudioDescriptions, + ifecyclemanagement: LifecycleManagement, + developertools: DeveloperTools, }; export const MODULE_MAP = { @@ -120,7 +116,7 @@ export default class FireboltExampleInvoker { } return await invoker(...params); } - + sdk = sdk.toLowerCase(); const moduleClass = MODULE_MAP[sdk][module]; const updatedMethod = removeSetInMethodName(methodName); diff --git a/src/IntentReader.js b/src/IntentReader.js index 3b79f6cf..c2aae195 100644 --- a/src/IntentReader.js +++ b/src/IntentReader.js @@ -35,7 +35,6 @@ import CallMethodHandler from './pubsub/handlers/CallMethodHandler'; import DataFetchHandler from './pubsub/handlers/DataFetchHandler'; import GetTestHandler from './pubsub/handlers/GetTestHandler'; import HealthCheckHandler from './pubsub/handlers/HealthCheckHandler'; -import RunTestHandler from './pubsub/handlers/RunTestHandler'; import RegisterEventHandler from './pubsub/handlers/RegisterEventHandler'; import ClearEventListeners from './pubsub/handlers/ClearEventListeners'; import ClearEventHandler from './pubsub/handlers/clearEventHandler'; @@ -44,6 +43,8 @@ import LifecycleRecordHandler from './pubsub/handlers/lifecycleRecordHandler'; import RegisterProviderHandler from './pubsub/handlers/RegisterProviderHandler'; import GetEventResponse from './pubsub/handlers/GetEventResponse'; import GetMethodResponseHandler from './pubsub/handlers/GetMethodResponseHandler'; +import LifecycleMethodHandler from './pubsub/handlers/LifecycleMethodHandler'; +import RunTestHandler from 'RunTestHandler'; const logger = require('./utils/Logger')('IntentReader.js'); @@ -61,6 +62,7 @@ const handlers = { stopLifecycleRecording: new LifecycleRecordHandler('stopLifecycleRecording'), getEventResponse: new GetEventResponse('getEventResponse'), getMethodResponse: new GetMethodResponseHandler('getMethodResponse'), + callLifecycle: new LifecycleMethodHandler('callLifecycle'), [CONSTANTS.CALL_METHOD]: new CallMethodHandler(CONSTANTS.CALL_METHOD), [CONSTANTS.HEALTH_CHECK]: new HealthCheckHandler(CONSTANTS.HEALTH_CHECK), }; @@ -83,6 +85,10 @@ export default class IntentReader { process.env.REPORTINGID = message.reportingId; } + if ('standalonePrefix' in message) { + process.env.STANDALONE_PREFIX = message.standalonePrefix; + } + const handler = handlers[message.task]; if (handler === undefined) { logger.info('Undefined handler: ' + message.task); diff --git a/src/LifeCycleHistory.js b/src/LifeCycleHistory.js index 3c5d32d0..588a43e1 100644 --- a/src/LifeCycleHistory.js +++ b/src/LifeCycleHistory.js @@ -16,14 +16,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Lifecycle, Parameters, Discovery } from '@firebolt-js/sdk'; +import { Lifecycle, Discovery } from '@firebolt-js/sdk'; import { BehaviorSubject } from 'rxjs'; require('dotenv').config(); import { CONSTANTS } from './constant'; import { getschemaValidationDone, getCurrentAppID } from './utils/Utils'; const logger = require('./utils/Logger')('LifeCycleHistory.js'); -import FireboltExampleInvoker from './FireboltExampleInvoker'; -import IntentReader from './IntentReader'; +import IntentReader from 'IntentReader'; let instance = null; let lifecycleValidation; @@ -105,6 +104,18 @@ export default class LifecycleHistory { if (event.data.query != undefined) { const intentReader = new IntentReader(); const query = JSON.parse(event.data.query); + if (query.params && query.params.appId && query.params.macaddress) { + // PUBSUB_CONNECTION environment variable has a pubsub client instance and calls the isConnected function to check the Websocket status. + if (!process.env.PUBSUB_CONNECTION || (process.env.PUBSUB_CONNECTION && !process.env.PUBSUB_CONNECTION.isConnected())) { + process.env.APP_TYPE = query.params.appType ? query.params.appType.toLowerCase() : CONSTANTS.FIREBOLT_CONST; + process.env.CURRENT_APPID = query.params.appId; + process.env.MACADDRESS = query.params.macaddress; + process.env.TEST_TOKEN = query.params.testtoken; + process.env.PUB_SUB_TOKEN = query.params.pubSubToken; + const pubSubListenerCreation = new PubSubCommunication(); + const webSocketConnection = await pubSubListenerCreation.startWebSocket(); + } + } if (query.task) { intentReader.processIntent(query); } diff --git a/src/MethodInvoker.js b/src/MethodInvoker.js index d57db90f..90b25172 100644 --- a/src/MethodInvoker.js +++ b/src/MethodInvoker.js @@ -29,9 +29,8 @@ const responseList = []; export class MethodInvoker { // This method accepts the message(method name, params) and return Api response with Schema validation result. async invoke(message) { - let response, method, params, mode, err, paramNames, module, methodObj; - let schemaMap; - let schemaValidationResult; + let response, method, params, mode, err, paramNames, module, methodObj, schemaMap, schemaValidationResult; + process.env.ID = process.env.ID + 1; process.env.COMMUNICATION_MODE = message.context.communicationMode; params = message.params.methodParams; if (message.params.method.includes('_')) { @@ -70,12 +69,14 @@ export class MethodInvoker { try { // Fetching the method Object from the deSchemaList if (method.includes('set') && params[0] == undefined && !CONSTANTS.METHODS_T0_IGNORE_WHICH_HAS_SET.includes(method)) { - methodObj = deSchemaList.methods.find((obj) => obj.name.toLowerCase() == updatedMethod.toLowerCase()); + methodObj = deSchemaList.methods.some((obj) => obj.name.toLowerCase() == updatedMethod.toLowerCase()); } else { - methodObj = deSchemaList.methods.find((obj) => obj.name.toLowerCase() == method.toLowerCase()); + methodObj = deSchemaList.methods.some((obj) => obj.name.toLowerCase() == method.toLowerCase()); } if (methodObj) { - schemaMap = methodObj.result.schema; + if (process.env.STANDALONE == true) { + schemaMap = methodObj.result.schema; + } const moduleClass = MODULE_MAP[invokedSdk][module]; if (moduleClass) { @@ -88,16 +89,22 @@ export class MethodInvoker { } else if (process.env.COMMUNICATION_MODE === CONSTANTS.TRANSPORT) { [response, err] = await handleAsyncFunction(FireboltTransportInvoker.get().invoke(method, params, paramNames), process.env.TimeoutInMS); } - schemaValidationResult = validator.validate(response, schemaMap); + if (process.env.STANDALONE == true) { + schemaValidationResult = validator.validate(response, schemaMap); + } } else if (!methodObj && process.env.COMMUNICATION_MODE === CONSTANTS.TRANSPORT) { [response, err] = await handleAsyncFunction(FireboltTransportInvoker.get().invoke(method, params, paramNames), process.env.TimeoutInMS); - schemaValidationResult = []; + if (process.env.STANDALONE == true) { + schemaValidationResult = []; + } } else { err = CONSTANTS.ERROR_MESSAGE_WRONG_METHOD_NAME; } - // if the method is not supported and it gives a valid response, validate against errorschema instead of api schema - if (message.params.isNotSupportedApi == true && response != undefined) { - schemaValidationResult = errorSchemaCheck(response); + if (process.env.STANDALONE == true) { + // if the method is not supported and it gives a valid response, validate against errorschema instead of api schema + if (message.params.isNotSupportedApi == true && response != undefined) { + schemaValidationResult = errorSchemaCheck(response); + } } } catch (error) { logger.error('Error: ', error); @@ -114,7 +121,15 @@ export class MethodInvoker { }; responseList.push(resultObject); - return this.formatResult(message.task, response, err, schemaValidationResult, params, schemaMap); + if (process.env.STANDALONE == true) { + return this.formatResult(message.task, response, err, schemaValidationResult, params, schemaMap); + } else { + if (err === undefined) { + return { jsonrpc: '2.0', result: response, id: process.env.ID }; + } else { + return { jsonrpc: '2.0', error: err, id: process.env.ID }; + } + } } formatResult(task, response, err, schemaValidationResult, params, schemaMap) { diff --git a/src/PubSubCommunication.js b/src/PubSubCommunication.js index 35822881..8bb20add 100644 --- a/src/PubSubCommunication.js +++ b/src/PubSubCommunication.js @@ -30,7 +30,7 @@ import { CONSTANTS } from './constant'; require('dotenv').config(); import pubSubClient from 'pubSubClient'; -import IntentReader from './IntentReader'; +import IntentReader from 'IntentReader'; const logger = require('./utils/Logger')('PubSubCommunication.js'); let client = null; diff --git a/src/Test_Runner.js b/src/Test_Runner.js index e23865bf..0528afce 100644 --- a/src/Test_Runner.js +++ b/src/Test_Runner.js @@ -36,14 +36,11 @@ const utils = require('./utils/Utils'); import LifecycleHistory from './LifeCycleHistory'; import { Device } from '@firebolt-js/sdk'; import { MODULE_MAP } from './FireboltExampleInvoker'; -import errorSchema from './source/errorSchema.json'; +import errorSchemaObject from './source/errorSchema.json'; const $RefParser = require('@apidevtools/json-schema-ref-parser'); const Validator = require('jsonschema').Validator; const validator = new Validator(); const logger = require('./utils/Logger')('Test_Runner.js'); - -let validationResult; -let validationError = {}; const TAG = '[Test_Runner]: '; /** @@ -100,7 +97,6 @@ export class Test_Runner { // Start time of all API invocation const resultStartTime = new Date(); let suiteStartTime = new Date(); - let errorSchemaResult; // This is the list of validation Results for each api ,This is the list that will be used for creating the report for (const executionMode of execModes) { @@ -155,11 +151,9 @@ export class Test_Runner { */ if (this.methodFilters.isMethodToBeExcluded(methodObj, communicationMode) || this.methodFilters.isRpcMethod(methodObj, invokedSdk, communicationMode)) { const obj = { - response: CONSTANTS.SKIPPED_MESSAGE, + error: CONSTANTS.SKIPPED_MESSAGE, param: undefined, - errorSchemaResult: undefined, methodWithExampleName: methodObj.name, - validationResult: {}, methodUuid: this.createUUID(), schemaData: schemaMap.schema, }; @@ -170,12 +164,10 @@ export class Test_Runner { overrideParamsFromTestData(method); for (let exampleIndex = 0; exampleIndex < method.examples.length; exampleIndex++) { let paramValues = []; - // The Subscribe methods are skipped for Transport, which is dynamically added from menubuilder - if (communicationMode == CONSTANTS.TRANSPORT) { - if (this.methodFilters.isSubscribeMethod(method.examples[exampleIndex]) || this.methodFilters.isSetMethod(method.examples[exampleIndex])) { - break; - } - } else if (this.methodFilters.isSetMethod(method.examples[exampleIndex])) { + if (this.methodFilters.isSubscribeMethod(method.examples[exampleIndex]) || this.methodFilters.isSetMethod(method.examples[exampleIndex])) { + break; + } + if (this.methodFilters.isSetMethod(method.examples[exampleIndex])) { continue; } @@ -190,11 +182,28 @@ export class Test_Runner { paramValues = example.params.map((p) => p.value); let result = null; + // Overriding the schema with the below format + const schemaFormat = { + type: 'object', + properties: {}, + }; + if (method.examples[exampleIndex].schema) { schemaMap = method.examples[exampleIndex]; } else { schemaMap = method.result; } + // Check if the method is an exception method + const isExceptionMethod = this.methodFilters.isExceptionMethod(methodObj.name, example.params); + const propertyKey = isExceptionMethod ? 'error' : 'result'; + + // If the schema already has a "properties" field and does not have "error" or "result", override the schema + if ((schemaMap.schema.hasOwnProperty('properties') && !schemaMap.schema.properties.hasOwnProperty(propertyKey)) || !schemaMap.schema.hasOwnProperty('properties')) { + schemaFormat.properties[propertyKey] = isExceptionMethod ? errorSchemaObject.errorSchema : schemaMap.schema; + schemaFormat.required = [propertyKey]; + schemaMap.schema = schemaFormat; + } + if (communicationMode == CONSTANTS.TRANSPORT) { const paramNames = method.params ? method.params.map((p) => p.name) : []; result = await this.apiInvoker(method.name, paramValues, executionMode, invokedSdk, paramNames); @@ -202,22 +211,16 @@ export class Test_Runner { result = await this.apiInvoker(method.name, paramValues, executionMode, invokedSdk); } - if (this.methodFilters.isExceptionMethod(methodObj.name, example.params)) { - if (method.examples[exampleIndex].schema) { - method.examples[exampleIndex].schema = errorSchema; - } else { - method.result.schema = errorSchema; - } - } - let schemaValidationResultForEachExample = method.examples[exampleIndex].schema ? validator.validate(result, method.examples[exampleIndex].schema) : validator.validate(result, method.result.schema); + const response = { result: result }; + let schemaValidationResultForEachExample = validator.validate(response, schemaMap.schema); if (this.methodFilters.isEventMethod(methodObj)) { - logger.info(TAG + `${methodObj.name} Result => ${JSON.stringify(result)}`, 'northBoundSchemaValidationAndReportGeneration'); - if (result && typeof result.includes === 'function' && result.includes('Successful')) { - schemaValidationResultForEachExample = {}; + logger.info(TAG + `${methodObj.name} Result => ${JSON.stringify(response)}`, 'northBoundSchemaValidationAndReportGeneration'); + if (response && response.result && typeof response.result.includes === 'function' && response.result.includes('Successful')) { + schemaValidationResultForEachExample = { errors: [] }; } } const schemaValidationResultForEachExampleSet = { - response: result, + response: response, param: example.params, validationResult: schemaValidationResultForEachExample, methodWithExampleName: methodWithExampleName, @@ -226,50 +229,30 @@ export class Test_Runner { }; schemaValidationResultSet.push(schemaValidationResultForEachExampleSet); } catch (error) { + const errorResponse = { error: error }; let obj; - if (schemaMap == undefined && error == CONSTANTS.UNDEFINED_RESPONSE_MESSAGE) { - logger.debug('TestContext Debug: Error block on api execution - Acceptable No result: ' + error + ' for method: ' + methodWithExampleName, 'northBoundSchemaValidationAndReportGeneration'); - errorSchemaResult = false; + if (error instanceof Error) { + errorResponse.error = error.message; + } + logger.debug('TestContext Debug: Error block on api execution - has error message: ' + errorResponse.error + ' for method: ' + methodWithExampleName, 'northBoundSchemaValidationAndReportGeneration'); + // Doing schema validation for error response only if schema is present + if (schemaMap.schema) { + const schemaValidationResult = validator.validate(errorResponse, schemaMap.schema); obj = { - response: 'No result object - Acceptable', + error: errorResponse, param: example.params, - errorSchemaResult: errorSchemaResult, methodWithExampleName: methodWithExampleName, - validationResult: {}, + validationResult: schemaValidationResult, methodUuid: methodUuid, schemaData: schemaMap.schema, }; - } else if (error.responseError) { - logger.debug('TestContext Debug: Error block on api execution - has responseError: ' + error + ' for method: ' + methodWithExampleName, 'northBoundSchemaValidationAndReportGeneration'); - const err = error.responseError; - errorSchemaResult = true; + } else { obj = { - error: error, + error: errorResponse, param: example.params, - errorSchemaResult: errorSchemaResult, methodWithExampleName: methodWithExampleName, methodUuid: methodUuid, - schemaData: schemaMap.schema, }; - } else { - logger.debug('TestContext Debug: Error block on api execution - has error message: ' + error + ' for method: ' + methodWithExampleName, 'northBoundSchemaValidationAndReportGeneration'); - if (this.methodFilters.isExceptionMethod(methodObj.name, example.params)) { - obj = this.errorCheckForExemptedMethods(error, methodObj, methodWithExampleName, example, schemaMap); - } else { - let err = error; - if (typeof error == 'string') { - err = { code: 'CertError', message: error }; - } - errorSchemaResult = false; - obj = { - error: err, - param: example.params, - errorSchemaResult: errorSchemaResult, - methodWithExampleName: methodWithExampleName, - methodUuid: methodUuid, - schemaData: schemaMap.schema, - }; - } } schemaValidationResultSet.push(obj); } @@ -277,12 +260,8 @@ export class Test_Runner { } else { // Adding on more element to err Object to display method name on the screen for multiple testcases logger.debug('TestContext Debug: could not find example for method: ' + methodWithExampleName, 'northBoundSchemaValidationAndReportGeneration'); - const err = { - code: 'CertError', - message: 'Could not find an example for ' + method.name, - }; const obj = { - error: err, + error: 'Could not find an example for ' + method.name, param: null, methodWithExampleName: methodObj.name, methodUuid: methodUuid, @@ -291,6 +270,7 @@ export class Test_Runner { schemaValidationResultSet.push(obj); } } + apiExecutionEndTime = new Date(); // api execution end time /** * Now we have executed and got the validation result for a particular api. * Either via the external module testCaseList execution or via the Example based invocation. @@ -511,11 +491,6 @@ export class Test_Runner { } else { if (response === undefined) { throw CONSTANTS.UNDEFINED_RESPONSE_MESSAGE; - } else if (response && response.error) { - const responseError = { - responseError: response, - }; - throw responseError; } } return response; @@ -552,13 +527,19 @@ export class Test_Runner { case CONSTANTS.LIFECYCLE_METHOD_LIST[0]: try { result = await this.lifecycleMethodCalls(method, params); - const stateSchema = this.getMethodSchema('Lifecycle.ready', lifecycleMethods); - schemaResult = this.schemaValidation(result.response, stateSchema); + if (process.env.STANDALONE == true) { + const stateSchema = this.getMethodSchema('Lifecycle.ready', lifecycleMethods); + schemaResult = this.schemaValidation(result.response, stateSchema); + } } catch (err) { error = err; result.error = error; } - response = this.createResultObject(result.response, result.error, schemaResult); + if (process.env.STANDALONE == true) { + response = this.createResultObject(result.response, result.error, schemaResult); + } else { + response = this.createResultObject(result.response, result.error); + } break; case CONSTANTS.LIFECYCLE_METHOD_LIST[1]: /* @@ -568,24 +549,36 @@ export class Test_Runner { */ try { result = await this.lifecycleMethodCalls(method, params); - const stateSchema = this.getMethodSchema('Lifecycle.state', lifecycleMethods); - schemaResult = this.schemaValidation(result.response, stateSchema); + if (process.env.STANDALONE == true) { + const stateSchema = this.getMethodSchema('Lifecycle.state', lifecycleMethods); + schemaResult = this.schemaValidation(result.response, stateSchema); + } } catch (err) { error = err; result.error = error; } - response = this.createResultObject(result.response, result.error, schemaResult); + if (process.env.STANDALONE == true) { + response = this.createResultObject(result.response, result.error, schemaResult); + } else { + response = this.createResultObject(result.response, result.error); + } break; case CONSTANTS.LIFECYCLE_METHOD_LIST[2]: try { result = await this.lifecycleMethodCalls(method, methods.methodParams); - const stateSchema = this.getMethodSchema('Lifecycle.close', lifecycleMethods); - schemaResult = this.schemaValidation(result.response, stateSchema); + if (process.env.STANDALONE == true) { + const stateSchema = this.getMethodSchema('Lifecycle.close', lifecycleMethods); + schemaResult = this.schemaValidation(result.response, stateSchema); + } } catch (err) { error = err; result.error = error; } - response = this.createResultObject(result.response, result.error, schemaResult); + if (process.env.STANDALONE == true) { + response = this.createResultObject(result.response, result.error, schemaResult); + } else { + response = this.createResultObject(result.response, result.error); + } break; case CONSTANTS.LIFECYCLE_METHOD_LIST[3]: @@ -606,52 +599,64 @@ export class Test_Runner { response = this.createResultObject(result, error); break; case CONSTANTS.LIFECYCLE_METHOD_LIST[5]: - try { - const OnInactiveEvent = LifecycleHistory.get(); - const OnInactiveHistory = OnInactiveEvent._history._value[0].event; - const OnInActiveList = this.getMethodSchema('Lifecycle.onInactive', lifecycleMethods); - schemaResult = this.schemaValidation(OnInactiveHistory, OnInActiveList); - if (OnInactiveHistory.state == 'inactive' && OnInactiveHistory.previous == 'initializing') { - contentResult = CONSTANTS.PASS; - } else { - contentResult = CONSTANTS.FAIL; + if (process.env.STANDALONE == true) { + try { + const OnInactiveEvent = LifecycleHistory.get(); + const OnInactiveHistory = OnInactiveEvent._history._value[0].event; + const OnInActiveList = this.getMethodSchema('Lifecycle.onInactive', lifecycleMethods); + schemaResult = this.schemaValidation(OnInactiveHistory, OnInActiveList); + if (OnInactiveHistory.state == 'inactive' && OnInactiveHistory.previous == 'initializing') { + contentResult = CONSTANTS.PASS; + } else { + contentResult = CONSTANTS.FAIL; + } + } catch (err) { + error = err; } - } catch (err) { - error = err; + response = this.createResultObject(result, error, schemaResult, contentResult); + } else { + response = this.createResultObject(result, error); } - response = this.createResultObject(result, error, schemaResult, contentResult); break; case CONSTANTS.LIFECYCLE_METHOD_LIST[6]: - try { - const onForegroundEvent = LifecycleHistory.get(); - const onForegroundHistory = onForegroundEvent._history._value[1].event; - const onForegroundList = this.getMethodSchema('Lifecycle.onForeground', lifecycleMethods); - schemaResult = this.schemaValidation(onForegroundHistory, onForegroundList); - if (onForegroundHistory.state == 'foreground' && onForegroundHistory.previous == 'inactive') { - contentResult = CONSTANTS.PASS; - } else { - contentResult = CONSTANTS.FAIL; + if (process.env.STANDALONE == true) { + try { + const onForegroundEvent = LifecycleHistory.get(); + const onForegroundHistory = onForegroundEvent._history._value[1].event; + const onForegroundList = this.getMethodSchema('Lifecycle.onForeground', lifecycleMethods); + schemaResult = this.schemaValidation(onForegroundHistory, onForegroundList); + if (onForegroundHistory.state == 'foreground' && onForegroundHistory.previous == 'inactive') { + contentResult = CONSTANTS.PASS; + } else { + contentResult = CONSTANTS.FAIL; + } + } catch (err) { + error = err; } - } catch (err) { - error = err; + response = this.createResultObject(result, error, schemaResult, contentResult); + } else { + response = this.createResultObject(result, error); } - response = this.createResultObject(result, error, schemaResult, contentResult); break; case CONSTANTS.LIFECYCLE_METHOD_LIST[7]: - try { - const onBackgroundEvent = LifecycleHistory.get(); - const onBackgroundHistory = onBackgroundEvent._history._value[2].event; - const onBackgroundList = this.getMethodSchema('Lifecycle.onBackground', lifecycleMethods); - schemaResult = this.schemaValidation(onBackgroundHistory, onBackgroundList); - if (onBackgroundHistory.state == 'background' && onBackgroundHistory.previous == 'foreground') { - contentResult = CONSTANTS.PASS; - } else { - contentResult = CONSTANTS.FAIL; + if (process.env.STANDALONE == true) { + try { + const onBackgroundEvent = LifecycleHistory.get(); + const onBackgroundHistory = onBackgroundEvent._history._value[2].event; + const onBackgroundList = this.getMethodSchema('Lifecycle.onBackground', lifecycleMethods); + schemaResult = this.schemaValidation(onBackgroundHistory, onBackgroundList); + if (onBackgroundHistory.state == 'background' && onBackgroundHistory.previous == 'foreground') { + contentResult = CONSTANTS.PASS; + } else { + contentResult = CONSTANTS.FAIL; + } + } catch (err) { + error = err; } - } catch (err) { - error = err; + response = this.createResultObject(result, error, schemaResult, contentResult); + } else { + response = this.createResultObject(result, error); } - response = this.createResultObject(result, error, schemaResult, contentResult); break; case CONSTANTS.LIFECYCLE_METHOD_LIST[8]: result = await this.lifecycleMethodCalls(method, params); @@ -709,13 +714,30 @@ export class Test_Runner { }; } - createResultObject(result, error, schemaResult, contentResult) { - const resultObject = { - result: result, - error: error, - schemaResult: schemaResult, - contentResult: contentResult, - }; + createResultObject(result, error) { + let resultObject; + if (process.env.STANDALONE == true) { + resultObject = { + result: result, + error: error, + schemaResult: schemaResult, + contentResult: contentResult, + }; + } else { + if (error == null) { + resultObject = { + jsonrpc: '2.0', + result: result, + id: process.env.ID + 1, + }; + } else { + resultObject = { + jsonrpc: '2.0', + error: error, + id: process.env.ID + 1, + }; + } + } return resultObject; } @@ -771,292 +793,136 @@ export class Test_Runner { state: 'skipped', }; let convertedResponse = null; - let convertedValidationErr = null; - let methodName; - let errorSchemaResult; - let uuid; let testContext = null; - - const doesContainMethodNotFound = CONSTANTS.ERROR_LIST.find((i) => - JSON.stringify(result || '') - .toLowerCase() - .includes(i.toLowerCase()) - ); + let convertedError = null; + const methodWithExampleName = result.methodWithExampleName; + const uuid = result.methodUuid; + let parsedResponse = result.error ? result.error : result.response; + let doesErrorMessageContainMethodNotFound = false; const params = result.param; - if (result.error || doesContainMethodNotFound) { - let errorMessage; - if (result.error && result.error.message) { - errorMessage = result.error.message; - } else { - errorMessage = CONSTANTS.WRONG_ERROR_MESSAGE_FORMAT; - result.error = CONSTANTS.WRONG_ERROR_MESSAGE_FORMAT; - } - const doesErrorMsgContainMethodNotFound = typeof errorMessage == 'string' && CONSTANTS.ERROR_LIST.find((i) => i.toLowerCase().includes(errorMessage.toLowerCase())); - - testContext = { - params: params, - result: null, - error: result.error, - }; - if (result.error.responseError) { - testContext.result = result.error.responseError; - testContext.error = null; - errorMessage = result.error.responseError.error; - } - - errorSchemaResult = result.errorSchemaResult; - if (errorMessage == undefined) { - errorMessage = 'undefined'; - } - - // for the below scenarios set the default status as failed + const methodName = result.methodWithExampleName.split('.')[0] + '.' + result.methodWithExampleName.split('.')[1]; + const isExceptionMethod = this.methodFilters.isExceptionMethod(methodName, params); + const schemaValidationResult = result.validationResult; + // Check if the error message contains "Method not found" + if (parsedResponse && parsedResponse.error && parsedResponse.error.message) { + doesErrorMessageContainMethodNotFound = CONSTANTS.ERROR_LIST.some((i) => + JSON.stringify(parsedResponse.error.message || '') + .toLowerCase() + .includes(i.toLowerCase()) + ); + } + testContext = { + params: params, + result: null, + error: null, + }; + if (!schemaValidationResult && result.error) { resultState = this.setResultState('failed'); - if (doesContainMethodNotFound && doesErrorMsgContainMethodNotFound) { - // When the underlying platform returns "Method not found" or "Not supported" in response.error.message. Certification suite will consider this as pending - errorMessage = JSON.stringify( - { - Schema: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Content: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Message: errorMessage, - params: params, - }, - null, - 1 - ); - // Disable SKIPPED and PENDING states in report based on flag - if (!process.env.CERTIFICATION) { - resultState = this.setResultState('pending'); - } - } else if (doesContainMethodNotFound && !doesErrorMsgContainMethodNotFound) { - // when the underlying platform returns "Method not found" or "Not supported" but in error. So not the correct error schema format. Certification will set the status as failed in this case - errorMessage = JSON.stringify({ Schema: CONSTANTS.FAILED, Content: CONSTANTS.FAILED, Message: JSON.stringify(result) }, null, 1); - } else if ((errorSchemaResult && typeof errorMessage == 'string') || typeof errorMessage == 'object') { - errorMessage = JSON.stringify( - { - Schema: CONSTANTS.FAILED, - Content: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Message: errorMessage, - Expected: schemaMap, - params: params, - }, - null, - 1 - ); - } else if (typeof errorMessage == 'string' || typeof errorMessage == 'object') { - errorMessage = JSON.stringify( - { - Schema: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Content: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Message: errorMessage, - Expected: schemaMap, - params: params, - }, - null, - 1 - ); - } - - // isPass = false - - convertedResponse = errorMessage; - convertedValidationErr = result.error.message; - methodName = result.methodWithExampleName; - uuid = result.methodUuid; - if (typeof result.error.message == 'string' || Array.isArray(result.error.message) || typeof result.error.message == 'undefined') { - convertedValidationErr = { err: result.error.message }; - } - } else { - testContext = { - params: params, - result: result.response, - error: null, - }; - const schemaValidationResult = result.validationResult; - const contentPending = (schemaValidationResult && schemaValidationResult.contentPending) || false; - let response = result.response; - methodName = result.methodWithExampleName; - uuid = result.methodUuid; - - if (response === CONSTANTS.SKIPPED_MESSAGE) { + convertedError = { err: parsedResponse }; + // Skipping the test case if the response having skipped message + if (parsedResponse === CONSTANTS.SKIPPED_MESSAGE) { resultState = this.setResultState('skipped'); - convertedValidationErr = { err: CONSTANTS.NO_ERROR_FOUND }; - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Content: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Message: response, - }, - null, - 1 - ); - } else if (response === undefined || (schemaValidationResult.errors && schemaValidationResult.errors.length > 0)) { - resultState = this.setResultState('failed'); - validationError = schemaValidationResult.errors; - convertedValidationErr = validationError; - if (typeof validationError == 'string' || Array.isArray(validationError) || typeof result.response == 'undefined') { - convertedValidationErr = { err: validationError }; - } - if (response === undefined) { - if (hasContentValidationExecuted) { - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.FAILED, - Content: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Message: { - Expected: schemaMap, - Actual: 'undefined', - Message: CONSTANTS.UNDEFINED_RESPONSE_MESSAGE, - params: params, - }, - }, - null, - 1 - ); + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.SCHEMA_CONTENT_SKIPPED, Message: parsedResponse }, null, 1); + } else { + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.SCHEMA_CONTENT_SKIPPED, Message: parsedResponse, Response: null, Expected: schemaMap, params: params }, null, 1); + } + } else if (isExceptionMethod) { + resultState = this.setResultState('failed'); + // Check if parsed response contains an error + if (parsedResponse && parsedResponse.error) { + testContext.error = parsedResponse.error; + convertedError = { err: parsedResponse.error }; + // If it is an exception method, and not as per schema, fail the test case. + if (schemaValidationResult && schemaValidationResult.errors && schemaValidationResult.errors.length > 0) { + // Response did not have error or result + if (parsedResponse.error == CONSTANTS.UNDEFINED_RESPONSE_MESSAGE) { + testContext.error = null; + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.FAILED, Message: CONSTANTS.NO_RESULT_OR_ERROR_MESSAGE, Response: null, Expected: schemaMap, params: params }, null, 1); } else { - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.FAILED, - Content: CONSTANTS.PENDING, - Message: { - Expected: schemaMap, - Actual: 'undefined', - Message: CONSTANTS.UNDEFINED_RESPONSE_MESSAGE, - params: params, - }, - }, - null, - 1 - ); + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.FAILED, Message: 'Expected error, incorrect error format', Response: parsedResponse, Expected: schemaMap, params: params }, null, 1); } } else { - response = utils.censorData(methodObj.name, response); - if (hasContentValidationExecuted) { - // Actual and Expected Schema/Content - if (schemaValidationResult.errors[0].message === 'Content is not valid') { - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.PASSED, - Content: CONSTANTS.FAILED, - Message: { - Expected: 'NA', - Actual: 'NA', - Error: schemaValidationResult.errors[0].message, - }, - params: params, - }, - null, - 1 - ); - } else { - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.FAILED, - Content: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Message: { Expected: schemaMap, Actual: response, Error: convertedValidationErr }, - params: params, - }, - null, - 1 - ); + // If error as per schema, error message contains method not found, marking the test case as pending or failed based on certification flag. + if (doesErrorMessageContainMethodNotFound) { + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.PASSED, Message: 'Method not implemented by platform', Response: parsedResponse, params: params }, null, 1); + // If the certification flag is enabled, fail the test case; otherwise, mark it as pending. + if (!process.env.CERTIFICATION) { + resultState = this.setResultState('pending'); } } else { - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.FAILED, - Content: CONSTANTS.PENDING, - Message: { - Expected: schemaMap, - Actual: response, - Error: schemaValidationResult.errors[0].message, - }, - params: params, - }, - null, - 1 - ); + // Exception method, and as per schema, marking the test case as passed. + resultState = this.setResultState('passed'); + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.PASSED, Message: 'Expected error, received error', Response: parsedResponse, params: params }, null, 1); } } } else { - // successfull validation - validationResult = CONSTANTS.PASSED; - // isPass = true - resultState = this.setResultState('passed'); - validationError = CONSTANTS.NO_ERROR_FOUND; - convertedValidationErr = validationError; - response = utils.censorData(methodObj.name, response); - if (typeof validationError == 'string' || Array.isArray(validationError)) { - convertedValidationErr = { err: validationError }; - } - if (hasContentValidationExecuted && !contentPending) { - if (process.env.TESTCONTEXT) { - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.PASSED, - Content: CONSTANTS.PASSED, - Message: response, - params: params, - }, - null, - 1 - ); - } else { - convertedResponse = JSON.stringify({ Schema: CONSTANTS.PASSED, Content: CONSTANTS.PASSED, params: params }, null, 1); + // Censoring the response for the specific method + parsedResponse = utils.censorData(methodObj.name, parsedResponse.result); + testContext.result = parsedResponse; + convertedError = { err: CONSTANTS.NO_ERROR_FOUND }; + // Expecting an error, but received a result, marking the test case as failed. + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.FAILED, Message: 'Expected error, received result', Response: { result: parsedResponse }, Expected: schemaMap, params: params }, null, 1); + } + } else { + resultState = this.setResultState('passed'); + // Check if the response is an error + if (parsedResponse && parsedResponse.error) { + testContext.error = parsedResponse.result; + convertedError = { err: parsedResponse }; + resultState = this.setResultState('failed'); + // If error message contains method not found, marking the test case as pending or failed based on certification flag. + if (doesErrorMessageContainMethodNotFound) { + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.FAILED, Message: 'Method not implemented by platform', Response: parsedResponse, Expected: schemaMap, params: params }, null, 1); + // If the certification flag is enabled, fail the test case; otherwise, mark it as pending. + if (!process.env.CERTIFICATION) { + resultState = this.setResultState('pending'); } + } + // Response did not have error or result + else if (parsedResponse.error == CONSTANTS.UNDEFINED_RESPONSE_MESSAGE) { + testContext.error = null; + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.FAILED, Message: CONSTANTS.NO_RESULT_OR_ERROR_MESSAGE, Response: null, Expected: schemaMap, params: params }, null, 1); } else { - if (process.env.TESTCONTEXT) { - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.PASSED, - Content: CONSTANTS.PENDING, - Message: response, - params: params, - }, - null, - 1 - ); - } else { - convertedResponse = JSON.stringify({ Schema: CONSTANTS.PASSED, Content: CONSTANTS.PENDING, params: params }, null, 1); - } + // Expecting an result, but received an error, marking the test case as failed. + convertedResponse = JSON.stringify( + { [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.FAILED, Message: 'Unexpected error encountered in the response', Response: parsedResponse, Expected: schemaMap, params: params }, + null, + 1 + ); } - if (response == 'No result object - Acceptable') { - if (process.env.TESTCONTEXT) { - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Content: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Message: response, - params: params, - }, - null, - 1 - ); - } else { - convertedResponse = JSON.stringify( - { - Schema: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - Content: CONSTANTS.SCHEMA_CONTENT_SKIPPED, - params: params, - }, - null, - 1 - ); - } + } else { + // Censoring the response for the specific method + parsedResponse = utils.censorData(methodObj.name, parsedResponse.result); + testContext.result = parsedResponse; + convertedError = { err: CONSTANTS.NO_ERROR_FOUND }; + // If the response is not as per schema, marking the test case as failed else passed. + if (schemaValidationResult && schemaValidationResult.errors && schemaValidationResult.errors.length > 0) { + resultState = this.setResultState('failed'); + convertedResponse = JSON.stringify( + { [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.FAILED, Message: schemaValidationResult.errors[0].stack, Response: { result: parsedResponse }, Expected: schemaMap, params: params }, + null, + 1 + ); + } else { + convertedResponse = JSON.stringify({ [CONSTANTS.SCHEMA_VALIDATION]: CONSTANTS.PASSED, Message: null, Response: { result: parsedResponse }, params: params }, null, 1); } } } - + if (typeof convertedError == 'string' || Array.isArray(convertedError) || typeof convertedError == 'undefined') { + convertedError = { err: convertedError }; + } !process.env.TESTCONTEXT ? (testContext = null) : (testContext = JSON.stringify(testContext, null, 1)); const apiInvocationDuration = apiExecutionEndTime - apiExecutionStartTime; const apiValidationResult = { - title: methodName, + title: methodWithExampleName, fullTitle: methodObj.name, duration: apiInvocationDuration, state: resultState.state.toLowerCase(), pass: resultState.bool.passed, fail: resultState.bool.failed, code: convertedResponse, - err: convertedValidationErr, + err: convertedError, uuid: uuid, parentUUID: suitesUuid, timedOut: false, @@ -1143,33 +1009,4 @@ export class Test_Runner { logger.info('Error occured while generating sdk version', err, 'getFireboltVersionFromSDK'); } } - - errorCheckForExemptedMethods(error, methodObj, methodWithExampleName, example, schemaMap) { - let obj; - const NOT_SUPPORTED_ERROR_MESSAGES = ['Unsupported', 'Not supported', 'not supported']; - const errMessage = '{"code":' + error.code + ',"message":' + error.message + '}'; - const schemaValidationResult = errorSchemaCheck(error); - if (schemaValidationResult && schemaValidationResult.errors && schemaValidationResult.errors.length > 0) { - obj = { - error: error, - param: example.params, - errorSchemaResult: true, - methodWithExampleName: methodWithExampleName, - methodUuid: this.createUUID(), - schemaData: errorSchema, - }; - } else { - NOT_SUPPORTED_ERROR_MESSAGES.some((errorMessage) => error.message.includes(errorMessage)); - obj = { - response: error, - param: example.params, - errorSchemaResult: undefined, - methodWithExampleName: methodWithExampleName, - validationResult: {}, - methodUuid: this.createUUID(), - schemaData: schemaMap.schema, - }; - } - return obj; - } } diff --git a/src/ValidationView.js b/src/ValidationView.js index d41d6c3e..edcba371 100644 --- a/src/ValidationView.js +++ b/src/ValidationView.js @@ -122,7 +122,7 @@ export default class ValidationView extends lng.Component { }, color: 0xff123456, }, - ContentValidationStateText: { + Message: { x: 530, y: 200, w: 1920 - 700, @@ -198,7 +198,7 @@ export default class ValidationView extends lng.Component { this.tag('UpdateText').text = CONSTANTS.VALIDATION_SCROLLMESSAGE; this.tag('ApititleText').text = ''; this.tag('SchemaValidationStateText').text = ''; - this.tag('ContentValidationStateText').text = ''; + this.tag('Message').text = ''; this.tag('ValidationData').text = ''; } } else { @@ -230,7 +230,7 @@ export default class ValidationView extends lng.Component { const { err, fail, code } = _displayparms; this.tag('ValidationData').color = 0xff123456; let schemaValidationStateText = null, - contentValidationStateText = null, + message = null, validationData = null; if (code != undefined) { let codeObject = null, @@ -238,37 +238,31 @@ export default class ValidationView extends lng.Component { messageString = null; try { codeObject = JSON.parse(_displayparms.code); - messageString = codeObject.Message; - if (typeof codeObject.Message != 'string') { - messageString = JSON.stringify(codeObject.Message, null, 1); + messageString = codeObject.Response; + if (typeof codeObject.Response != 'string') { + messageString = JSON.stringify(codeObject.Response, null, 1); } isCodeTypeObject = true; } catch (err) { isCodeTypeObject = false; } if (isCodeTypeObject) { - schemaValidationStateText = CONSTANTS.SCHEMA_VALIDATION_STATUSMESSAGE + codeObject.Schema; - contentValidationStateText = CONSTANTS.CONTENT_VALIDATION_STATUSMESSAGE + codeObject.Content; - if (fail) { - validationData = CONSTANTS.ERROR_MESSAGE + messageString; - } else { - validationData = CONSTANTS.API_RESPONSE + messageString; - } + schemaValidationStateText = CONSTANTS.SCHEMA_VALIDATION_STATUSMESSAGE + codeObject['Schema Validation']; + message = 'Message: ' + codeObject.Message; + validationData = CONSTANTS.API_RESPONSE + messageString; } else { schemaValidationStateText = CONSTANTS.SCHEMA_VALIDATION_STATUSMESSAGE + CONSTANTS.SCHEMA_CONTENT_SKIPPED; - contentValidationStateText = CONSTANTS.CONTENT_VALIDATION_STATUSMESSAGE + CONSTANTS.SCHEMA_CONTENT_SKIPPED; - validationData = CONSTANTS.ERROR_MESSAGE + 'JSON parse failed (ValidationView)'; + validationData = CONSTANTS.API_RESPONSE + 'JSON parse failed (ValidationView)'; } } else { // Remove if not needed after testing schemaValidationStateText = CONSTANTS.SCHEMA_VALIDATION_STATUSMESSAGE + CONSTANTS.SCHEMA_CONTENT_SKIPPED; - contentValidationStateText = CONSTANTS.CONTENT_VALIDATION_STATUSMESSAGE + CONSTANTS.SCHEMA_CONTENT_SKIPPED; - validationData = CONSTANTS.ERROR_MESSAGE + 'Received response as undefined'; + validationData = CONSTANTS.API_RESPONSE + 'Received response as undefined'; } // Updating values in UI this.tag('ApititleText').text = CONSTANTS.API_TITLE + _displayparms.fullTitle; this.tag('SchemaValidationStateText').text = schemaValidationStateText; - this.tag('ContentValidationStateText').text = contentValidationStateText; + this.tag('Message').text = message; /* Schema data for some APIs are large enough to break the render engine. @@ -282,7 +276,6 @@ export default class ValidationView extends lng.Component { } this.tag('ValidationData').text = validationData; } - // Handling keyboard inputs for shifting focus _handleUp() { this._setState('ExecuteButton'); diff --git a/src/constant.js b/src/constant.js index a4c721d8..4b8e9bad 100644 --- a/src/constant.js +++ b/src/constant.js @@ -17,7 +17,7 @@ */ import CONFIG_CONSTANTS from 'config'; -import CORE_OPEN_RPC from '@firebolt-js/sdk/dist/firebolt-core-open-rpc'; +import CORE_OPEN_RPC from '@firebolt-js/sdk/dist/firebolt-open-rpc'; import MANAGE_OPEN_RPC from '@firebolt-js/manage-sdk/dist/firebolt-manage-open-rpc'; export const CONSTANTS = { ALL_SDKS: 'ALL SDKS', @@ -49,24 +49,24 @@ export const CONSTANTS = { APP_NAVIGATION_MESSENGER: 'MESSENGER', SCROLL_MENU_MESSAGE: "Scroll down through the menu's to view the result", LIFECYCLE_METHOD_LIST: [ - 'Lifecycle.ready', - 'Lifecycle.state', - 'Lifecycle.close', - 'Lifecycle.finished', - 'Lifecycle.history', - 'Lifecycle.onInactive', - 'Lifecycle.onForeground', - 'Lifecycle.onBackground', - 'Lifecycle.finished', - 'Lifecycle.schema', - 'Lifecycle.background', - 'Lifecycle.suspend', - 'Lifecycle.unsuspend', + 'lifecycle.ready', + 'lifecycle.state', + 'lifecycle.close', + 'lifecycle.finished', + 'lifecycle.history', + 'lifecycle.onInactive', + 'lifecycle.onForeground', + 'lifecycle.onBackground', + 'lifecycle.finished', + 'lifecycle.schema', + 'lifecycle.background', + 'lifecycle.suspend', + 'lifecycle.unsuspend', ], CONTENT_ERROR: 'Content Error', RDK_SERVICES: 'org.rdk.', API_TITLE: 'API TITLE: ', - API_RESPONSE: 'API Response: ', + API_RESPONSE: 'Response: ', INVOKE_TEST_MESSAGE: '**** Click Invoke to run tests ****', VALIDATION_MESSAGE: '***** Validation Started ******', VALIDATION_SCROLLMESSAGE: "Scroll down through the menu's to view the result", @@ -113,7 +113,7 @@ export const CONSTANTS = { EXCLUDED_VALUES: [null, undefined], FIREBOLT_CONST: 'firebolt', CERTIFICATION: false, - METHODS_T0_IGNORE_WHICH_HAS_SET: ['privacy.settings', 'securestorage.setForApp'], + METHODS_T0_IGNORE_WHICH_HAS_SET: ['privacy.settings', 'securestorage.setForApp', 'lifecyclemanagement.setState'], ERROR_MESSAGEREGEX: new RegExp('((-)[0-9]{5}): ([A-Za-z ]*)'), LOCK_TIME: 20000, MAX_FAILURES: 3, @@ -160,4 +160,6 @@ export const CONSTANTS = { EXCLUDED_METHODS_FOR_MFOS: [], ...CONFIG_CONSTANTS, VERSIONS: 'Versions', + NO_RESULT_OR_ERROR_MESSAGE: 'No result or error in response. eg: {jsonrpc: "2.0", id: x }', + SCHEMA_VALIDATION: 'Schema Validation', }; diff --git a/src/pubsub/handlers/CallMethodHandler.js b/src/pubsub/handlers/CallMethodHandler.js index 63fc7ccd..ac98b8bb 100644 --- a/src/pubsub/handlers/CallMethodHandler.js +++ b/src/pubsub/handlers/CallMethodHandler.js @@ -48,13 +48,21 @@ export default class CallMethodHandler extends BaseHandler { process.env.TimeoutInMS = message.responseTimeout ? message.responseTimeout : null; const result = await invoker.invoke(message); - return JSON.stringify({ report: result }); + if (process.env.STANDALONE == true) { + return JSON.stringify({ report: result }); + } else { + return JSON.stringify(result); + } } catch (e) { const result = { responseCode: CONSTANTS.STATUS_CODE[1], error: { message: 'FCA in exception block: ' + e.message, code: 'FCAError' }, }; - return JSON.stringify({ report: result }); + if (process.env.STANDALONE == true) { + return JSON.stringify({ report: result }); + } else { + return JSON.stringify(result); + } } } } diff --git a/src/pubsub/handlers/GetEventResponse.js b/src/pubsub/handlers/GetEventResponse.js index 4a9ff571..06713b7c 100644 --- a/src/pubsub/handlers/GetEventResponse.js +++ b/src/pubsub/handlers/GetEventResponse.js @@ -28,6 +28,11 @@ export default class GetEventResponse extends BaseHandler { async handle(message) { const eventInvocation = new EventInvocation(); const validationReport = eventInvocation.getEventResponse(message); - return JSON.stringify({ report: validationReport }); + if (process.env.STANDALONE == true) { + return JSON.stringify({ report: validationReport }); + } else { + const validationReportObject = { jsonrpc: '2.0', result: validationReport, id: process.env.ID + 1 }; + return JSON.stringify(validationReportObject); + } } } diff --git a/src/pubsub/handlers/LifecycleMethodHandler.js b/src/pubsub/handlers/LifecycleMethodHandler.js new file mode 100644 index 00000000..b7f84bb3 --- /dev/null +++ b/src/pubsub/handlers/LifecycleMethodHandler.js @@ -0,0 +1,54 @@ +/** + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import BaseHandler from './BaseHandler'; +import { Test_Runner } from 'Test_Runner'; +const logger = require('../../utils/Logger')('LifecycleMethodHandler.js'); +import { CONSTANTS } from '../../constant'; + +const { v4: uuidv4 } = require('uuid'); +require('dotenv').config(); + +export default class LifecycleMethodHandler extends BaseHandler { + constructor(handlerName) { + super(handlerName); + } + async handle(message) { + process.env.COMMUNICATION_MODE = message.context.communicationMode; + process.env.APP_TYPE = message.params.appType[0]; + const sdkInvokerInfo = new Test_Runner(); + try { + const lifecycleApiResponse = await sdkInvokerInfo.invokeLifecycleAPI(message.params); + if (process.env.STANDALONE == true) { + return JSON.stringify({ report: lifecycleApiResponse }); + } else { + return JSON.stringify(lifecycleApiResponse); + } + } catch (e) { + const result = { + responseCode: CONSTANTS.STATUS_CODE[1], + error: { message: 'FCA in exception block: ' + e.message, code: 'FCAError' }, + }; + if (process.env.STANDALONE == true) { + return JSON.stringify({ report: result }); + } else { + return JSON.stringify(result); + } + } + } +} diff --git a/src/pubsub/handlers/RegisterEventHandler.js b/src/pubsub/handlers/RegisterEventHandler.js index a7c176ab..efe7317c 100644 --- a/src/pubsub/handlers/RegisterEventHandler.js +++ b/src/pubsub/handlers/RegisterEventHandler.js @@ -29,7 +29,11 @@ export default class RegisterEventHandler extends BaseHandler { async handle(message) { const validationReport = await this.eventSwitchMode(message); - return JSON.stringify({ report: validationReport }); + if (process.env.STANDALONE == true) { + return JSON.stringify({ report: validationReport }); + } else { + return JSON.stringify(validationReport); + } } async eventSwitchMode(message) { diff --git a/src/pubsub/handlers/RunTestHandler.js b/src/pubsub/handlers/RunTestHandler.js index 507aa747..233234c4 100644 --- a/src/pubsub/handlers/RunTestHandler.js +++ b/src/pubsub/handlers/RunTestHandler.js @@ -94,7 +94,7 @@ export default class RunTestHandler extends BaseHandler { async getValidationReport(message) { const sdkMode = message.action; - process.env.COMMUNICATION_MODE = message.context.communicationMode; + process.env.COMMUNICATION_MODE = message.context.communicationMode ? message.context.communicationMode : CONSTANTS.TRANSPORT; const sdkInvokerInfo = new Test_Runner(); let validatedMenu; diff --git a/src/source/errorSchema.json b/src/source/errorSchema.json index 65da8251..4479d801 100644 --- a/src/source/errorSchema.json +++ b/src/source/errorSchema.json @@ -1,22 +1,23 @@ { - "oneOf": [ - { - "type": "object", - "properties": { - "code": { - "type": "number" - }, - "message": { - "type": "string" - } - }, - "required": [ - "code", - "message" - ] + "errorSchema": { + "type": "object", + "additionalProperties": false, + "required": ["code", "message"], + "properties": { + "code": { + "title": "errorObjectCode", + "description": "A Number that indicates the error type that occurred. This MUST be an integer. The error codes from and including -32768 to -32000 are reserved for pre-defined errors. These pre-defined errors SHOULD be assumed to be returned from any JSON-RPC api.", + "type": "integer" }, - { - "type": "string" + "message": { + "title": "errorObjectMessage", + "description": "A String providing a short description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + }, + "data": { + "title": "errorObjectData", + "description": "A Primitive or Structured value that contains additional information about the error. This may be omitted. The value of this member is defined by the Server (e.g. detailed error information, nested errors etc.)." } - ] -} \ No newline at end of file + } + } + } \ No newline at end of file diff --git a/src/utils/Utils.js b/src/utils/Utils.js index c1f8bb29..0b5efe4d 100644 --- a/src/utils/Utils.js +++ b/src/utils/Utils.js @@ -18,7 +18,7 @@ import { CONSTANTS } from '../constant'; import FireboltExampleInvoker from '../FireboltExampleInvoker'; -import errorSchema from '../source/errorSchema.json'; +import errorSchemaObject from '../source/errorSchema.json'; const { v4: uuidv4 } = require('uuid'); const $RefParser = require('@apidevtools/json-schema-ref-parser'); @@ -28,6 +28,7 @@ const xml2js = require('xml2js'); const logger = require('../utils/Logger')('Utils.js'); let deSchemaList, invokedSdk; +const uuid = uuidv4().replace(/-/g, ''); /** * This function returns a list with the result (if successful) or the error (on failure), @@ -118,6 +119,7 @@ function censorData(methodName, response) { let json; try { json = require('../../plugins/external-test-data/fixtures/censorData.json'); + methodName = methodName.charAt(0).toUpperCase() + methodName.slice(1); if (methodName in json) { for (let i = 0; i < json[methodName].field.length; i++) { if (response[json[methodName].field[i]]) { @@ -147,45 +149,54 @@ function pushReportToS3(report) { try { const parser = new xml2js.Parser(); - [result, err] = await handleAsyncFunction(FireboltExampleInvoker.get().invoke(CONSTANTS.CORE.toLowerCase(), 'Authentication.token', ['device'])); let parsingSuccessful = false; - - if (result && result.value && !err) { - const bufferObj = Buffer.from(result.value, 'base64'); - const xmlData = bufferObj.toString('utf8'); - parser.parseString(xmlData, function (err, result) { - if (err) { - parsingSuccessful = false; - } else { - const res = result['ns2:xcal-auth-message']['attribute']; - for (const resItem of res) { - if (resItem.$.key === 'device:ccpPki:estbMac') { - logger.info(resItem._, 'pushReportToS3'); - macAddress = resItem._; + if (!process.env.MACADDRESS) { + [result, err] = await handleAsyncFunction(FireboltExampleInvoker.get().invoke(CONSTANTS.CORE.toLowerCase(), 'Authentication.root', [])); + if (result && result.value && !err) { + const bufferObj = Buffer.from(result.value, 'base64'); + const xmlData = bufferObj.toString('utf8'); + await new Promise((resolve, reject) => { + parser.parseString(xmlData, function (err, result) { + if (err) { + parsingSuccessful = false; + resolve(); + } else { + const res = result['ns2:xcal-auth-message']['attribute']; + parsingSuccessful = true; + for (const resItem of res) { + if (resItem.$.key === 'device:ccpPki:estbMac') { + logger.info(resItem._, 'pushReportToS3'); + macAddress = resItem._; + } + } + resolve(); } - } - macAddress = macAddress.split(':').join(''); - reportName = macAddress + '-' + 'refAppExecReport' + '-' + fileNameAppend; - parsingSuccessful = true; - } - }); + }); + }); + } + } else { + macAddress = process.env.MACADDRESS; + parsingSuccessful = true; } + macAddress = macAddress.split(':').join(''); + reportName = macAddress + '-' + 'refAppExecReport' + '-' + fileNameAppend; + if (parsingSuccessful && process.env.REPORTINGID && process.env.STANDALONE) { reportName = process.env.REPORTINGID + '-' + 'refAppExecReport' + '-' + fileNameAppend; } - if (!parsingSuccessful) { + if (typeof parsingSuccessful !== 'undefined' && !parsingSuccessful) { reportName = process.env.REPORTINGID && process.env.STANDALONE ? process.env.REPORTINGID + '-' + 'refAppExecReport' + '-' + fileNameAppend : !process.env.REPORTINGID && process.env.STANDALONE - ? uuidv4() + '-' + 'refAppExecReport' + '-' + fileNameAppend + ? uuid + '-' + 'refAppExecReport' + '-' + fileNameAppend : 'refAppExecReport' + '-' + fileNameAppend; } } catch (error) { logger.error(error, 'pushReportToS3'); - reportName = process.env.REPORTINGID && process.env.STANDALONE ? process.env.REPORTINGID + '-' + 'refAppExecReport' + '-' + fileNameAppend : 'refAppExecReport' + '-' + fileNameAppend; + reportName = process.env.REPORTINGID && process.env.STANDALONE ? process.env.REPORTINGID + '-' + 'refAppExecReport' + '-' + fileNameAppend : uuid + '-' + 'refAppExecReport' + '-' + fileNameAppend; } let restApiUrl = CONSTANTS.REPORT_PUBLISH_URL + reportName + '.json'; @@ -194,10 +205,11 @@ function pushReportToS3(report) { // Uplaods to standalone url if standalone param is passed in url if (process.env.STANDALONE == 'true') { + const prefix = process.env.STANDALONE_PREFIX ? process.env.STANDALONE_PREFIX : 'standaloneReports'; const reportNameSplit = reportName.split('-'); const reportId = reportNameSplit[0]; - restApiUrl = CONSTANTS.REPORT_PUBLISH_STANDALONE_URL + reportName + '.json'; - logger.info(`You will be able to access your report shortly at: ${CONSTANTS.REPORT_PUBLISH_STANDALONE_REPORT_URL}${reportId}/report.html`, 'pushReportToS3'); + restApiUrl = CONSTANTS.REPORT_PUBLISH_STANDALONE_URL + prefix + '-' + reportName + '.json'; + logger.info(`You will be able to access your report shortly at: ${CONSTANTS.REPORT_PUBLISH_STANDALONE_REPORT_URL}${prefix}/${reportId}/report.html`, 'pushReportToS3'); } logger.info('URL: ' + restApiUrl, 'pushReportToS3'); @@ -284,8 +296,8 @@ function filterExamples(programlist, programType, offeringType) { function errorSchemaCheck(err) { let schemaValidationResult; - if (errorSchema) { - schemaValidationResult = validator.validate(err, errorSchema); + if (errorSchemaObject.errorSchema) { + schemaValidationResult = validator.validate(err, errorSchemaObject.errorSchema); } return schemaValidationResult; } @@ -317,7 +329,7 @@ function removeSetInMethodName(apiName) { * @description get the current appid with Advertising.appBundleId */ async function getCurrentAppID() { - if (!process.env.CURRENT_APPID || !process.env.APPID) { + if (!process.env.CURRENT_APPID) { try { let res = await FireboltExampleInvoker.get().invoke(CONSTANTS.CORE.toLowerCase(), 'Advertising.appBundleId', []); const lastIndex = res.lastIndexOf('.'); @@ -386,7 +398,7 @@ async function overrideParamsFromTestData(methodObj) { try { const paramsJson = testDataHandler('overrideParams'); if (paramsJson && typeof paramsJson == 'object' && Object.keys(paramsJson).length) { - const appID = process.env.APPID; + const appID = process.env.CURRENT_APPID; // Checking if any data present for the passed appId const parsedMethod = paramsJson[appID]; // Fetching the examples from the parsedMethod diff --git a/test/jest.config.js b/test/jest.config.js index f236cc31..ea8c200e 100644 --- a/test/jest.config.js +++ b/test/jest.config.js @@ -32,6 +32,8 @@ module.exports = { '^config$': '../plugins/config.js', '^Test_Runner$': '/../src/Test_Runner.js', '^EventInvocation$': '/../src//EventInvocation.js', + '^IntentReader$': '../src/IntentReader.js', + '^RunTestHandler$': '/../src/pubsub/handlers/RunTestHandler.js', }, collectCoverage: true, coverageThreshold: { diff --git a/test/unit/CallMethodHandler.test.js b/test/unit/CallMethodHandler.test.js index f7bcfbef..b1158735 100644 --- a/test/unit/CallMethodHandler.test.js +++ b/test/unit/CallMethodHandler.test.js @@ -27,20 +27,9 @@ jest.mock('../../src/MethodInvoker', () => { switch (message.params.method) { case 'firebolt.mockMethod': return { - method: 'callMethod', - params: [], - responseCode: 0, - apiResponse: { result: 'mockFireboltResult', error: null }, - schemaValidationStatus: 'PASS', - schemaValidationResponse: { - instance: 'mockFireboltResult', - schema: { type: 'string' }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }, + id: 1, + result: 'mockFireboltResult', + jsonrpc: '2.0', }; default: throw new Error('Firebolt error'); @@ -55,20 +44,9 @@ jest.mock('externalInvokers', () => ({ myExternalInvoker: function () { this.invoke = () => Promise.resolve({ - method: 'callMethod', - params: [], - responseCode: 0, - apiResponse: { result: 'mockResult', error: null }, - schemaValidationStatus: 'PASS', - schemaValidationResponse: { - instance: 'mockResult', - schema: { type: 'string' }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }, + id: 1, + result: 'mockResult', + jsonrpc: '2.0', }); }, })); @@ -104,9 +82,9 @@ describe('CallMethodHandler', () => { const responseString = await callMethodHandler.handle(message); console.log(expect.getState().currentTestName + ' : ' + responseString); expect(responseString).toBeTruthy(); - expect(responseString).toContain('report'); + expect(responseString).toContain('result'); const report = JSON.parse(responseString); - expect(report.report.apiResponse.result).toEqual('mockResult'); + expect(report.result).toEqual('mockResult'); }); test('Validate method invoker is invoked', async () => { const message = { @@ -123,9 +101,9 @@ describe('CallMethodHandler', () => { const responseString = await callMethodHandler.handle(message); console.log(expect.getState().currentTestName + ' : ' + responseString); expect(responseString).toBeTruthy(); - expect(responseString).toContain('report'); + expect(responseString).toContain('result'); const report = JSON.parse(responseString); - expect(report.report.apiResponse.result).toEqual('mockFireboltResult'); + expect(report.result).toEqual('mockFireboltResult'); }); test('Validate exception handling', async () => { const message = { @@ -141,9 +119,8 @@ describe('CallMethodHandler', () => { const responseString = await callMethodHandler.handle(message); console.log(expect.getState().currentTestName + ' : ' + responseString); const report = JSON.parse(responseString); - expect(report.report.responseCode).toEqual(1); // indicating failure - expect(report.report.error.code).toEqual('FCAError'); // indicating failure is within FCA app - expect(report.report.error.message).toContain('FCA in exception block'); // indicating failure is within FCA app + expect(report.error.code).toEqual('FCAError'); // indicating failure is within FCA app + expect(report.error.message).toContain('FCA in exception block'); // indicating failure is within FCA app }); }); }); diff --git a/test/unit/EventInvocation.test.js b/test/unit/EventInvocation.test.js index 1a8b3e4e..143b5702 100644 --- a/test/unit/EventInvocation.test.js +++ b/test/unit/EventInvocation.test.js @@ -534,7 +534,7 @@ describe('EventInvocation', () => { const eventParams = { params: { event: 'mocksdk_mockmodule.onmodulechanged' } }; // register listener let result = await eventInvocation.northBoundEventHandling(eventParams); - expect(result.eventListenerId).toBeDefined(); + expect(result.id).toBeDefined(); // check no errors when no listeners are registered result = eventInvocation.clearAllListeners(); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); @@ -546,11 +546,11 @@ describe('EventInvocation', () => { const eventParams = { params: { event: 'mocksdk_mockmodule.onmodulechanged' } }; // register listener let result = await eventInvocation.northBoundEventHandling(eventParams); - expect(result.eventListenerId).toBeDefined(); + expect(result.id).toBeDefined(); // register second listener listener const eventParams1 = { params: { event: 'mocksdk_mockeventmodule.oneventmodulechanged' } }; const response = await eventInvocation.northBoundEventHandling(eventParams1); - expect(response.eventListenerId).toBeDefined(); + expect(response.id).toBeDefined(); // check no errors when no listeners are registered result = eventInvocation.clearAllListeners(); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); @@ -572,17 +572,18 @@ describe('EventInvocation', () => { test('validate EventInvocation method with communicationMode SDK', async () => { const eventParams = { params: { event: 'mocksdk_mockmodule.onmodulechanged' } }; const expectedResponse = { - eventName: 'mocksdk_mockmodule.onmodulechanged', - eventListenerId: 'mockmodule.onmodulechanged-1', - eventListenerResponse: { listenerResponse: 1, error: null }, - eventListenerSchemaResult: { status: 'PASS', eventSchemaResult: {} }, + jsonrpc: '2.0', + result: { + listening: true, + event: 'mocksdk_mockmodule.onmodulechanged', + }, + id: 4, }; const result = await eventInvocation.northBoundEventHandling(eventParams); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(MODULE_MAP.mocksdk.mockmodule.listen).toHaveBeenCalled(); - // expect(result.eventListenerId).toBe(expectedResponse.eventListenerId) - expect(result.eventListenerResponse.error).toBeNull(); - expect(result.eventListenerSchemaResult.status).toEqual(expectedResponse.eventListenerSchemaResult.status); + expect(result.id).toBe(expectedResponse.id); + expect(result.result).not.toBeNull(); }); test('should fail if not supported api returns a valid response and not error object', async () => { @@ -590,46 +591,17 @@ describe('EventInvocation', () => { params: { event: 'mocksdk_mockmodule.onmodulechanged', isNotSupportedApi: true }, }; const expectedResponse = { - eventName: 'mocksdk_mockmodule.onmodulechanged', - eventListenerId: 'mockmodule.onmodulechanged-1', - eventListenerResponse: { listenerResponse: 1, error: null }, - eventListenerSchemaResult: { - status: 'FAIL', - eventSchemaResult: { - instance: 1, - schema: { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [ - { - path: [], - property: 'instance', - message: 'is not of a type(s) object', - schema: { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - instance: 1, - name: 'type', - argument: ['object'], - stack: 'instance is not of a type(s) object', - }, - ], - disableFormat: false, - }, + jsonrpc: '2.0', + result: { + listening: true, + event: 'mocksdk_mockmodule.onmodulechanged', }, + id: 4, }; const result = await eventInvocation.northBoundEventHandling(eventParams); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(MODULE_MAP.mocksdk.mockmodule.listen).toHaveBeenCalled(); - expect(result.eventListenerResponse.error).toStrictEqual(expectedResponse.eventListenerResponse.error); - expect(result.eventListenerSchemaResult.status).toEqual(expectedResponse.eventListenerSchemaResult.status); + expect(result.error).toStrictEqual(expectedResponse.error); }); test('should pass if not supported api returns an error object', async () => { @@ -638,66 +610,34 @@ describe('EventInvocation', () => { isNotSupportedApi: true, }; const expectedResponse = { - eventName: 'mocksdk_mockmodule.onnotsupported', - eventListenerId: null, - eventListenerResponse: { - result: null, - error: { code: -52001, message: 'Method not supported' }, - }, - eventListenerSchemaResult: { - status: 'PASS', - eventSchemaResult: { - instance: { code: -52001, message: 'Method not supported' }, - schema: { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }, + jsonrpc: '2.0', + error: { + code: -52001, + message: 'Method not supported', }, + id: 16, }; const result = await eventInvocation.northBoundEventHandling(eventParams); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(MODULE_MAP.mocksdk.mockmodule.listen).toHaveBeenCalled(); - expect(result.eventListenerResponse.error).toStrictEqual(expectedResponse.eventListenerResponse.error); - expect(result.eventListenerSchemaResult.status).toEqual(expectedResponse.eventListenerSchemaResult.status); + expect(result.error).toStrictEqual(expectedResponse.error); }); test('validate invalid EventInvocation method with communicationMode SDK - Method not found', async () => { const eventParams = { params: { event: 'mocksdk_mockmodule.oninvalidevent' } }; const expectedResponse = { - eventName: 'mocksdk_mockmodule.oninvalidevent', - eventListenerId: null, - responseCode: 3, - eventListenerResponse: { result: null, error: { code: '', message: 'Method not found' } }, - eventListenerSchemaResult: { status: 'FAIL', eventSchemaResult: {} }, - }; - const result = await eventInvocation.northBoundEventHandling(eventParams); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(MODULE_MAP.mocksdk.mockmodule.listen).toHaveBeenCalled(); - expect(result.eventListenerId).toBeNull(); - expect(result.responseCode).toBe(expectedResponse.responseCode); - expect(result.eventListenerResponse).toStrictEqual(expectedResponse.eventListenerResponse); - expect(result.eventListenerSchemaResult.status).toEqual(expectedResponse.eventListenerSchemaResult.status); - }); - test('validate invalid EventInvocation method with communicationMode SDK - schema failure', async () => { - const eventParams = { params: { event: 'mocksdk_mockmodule.oninvalidschema' } }; - const expectedResponse = { - eventName: 'mocksdk_mockmodule.oninvalidschema', - eventListenerId: 'mockmodule.oninvalidschema-[object Object]', - eventListenerResponse: { result: null, error: { listen: 2 } }, - eventListenerSchemaResult: { status: 'FAIL', eventSchemaResult: {} }, + jsonrpc: '2.0', + error: { + code: '', + message: 'Method not found', + }, + id: 16, }; const result = await eventInvocation.northBoundEventHandling(eventParams); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(MODULE_MAP.mocksdk.mockmodule.listen).toHaveBeenCalled(); - expect(result.eventListenerResponse).toStrictEqual(expectedResponse.eventListenerResponse); - expect(result.eventListenerSchemaResult.status).toEqual(expectedResponse.eventListenerSchemaResult.status); + expect(result.id).toBeNull(); + expect(result.error).toStrictEqual(expectedResponse.error); }); test('validate getEventResponse method', async () => { const message = { params: { event: 'lifecycle.onForeground' } }; @@ -708,17 +648,18 @@ describe('EventInvocation', () => { process.env.COMMUNICATION_MODE = 'Transport'; const eventParams = { params: { event: 'mocksdk_mockmodule.onmodulechanged' } }; const expectedResponse = { - eventName: 'mocksdk_mockmodule.onmodulechanged', - eventListenerId: 'mockmodule.onmodulechanged-1', - eventListenerResponse: { listenerResponse: 1, error: null }, - eventListenerSchemaResult: { status: 'PASS', eventSchemaResult: {} }, + jsonrpc: '2.0', + result: { + listening: true, + event: 'mocksdk_mockmodule.onmodulechanged', + }, + id: 1, }; const result = await eventInvocation.northBoundEventHandling(eventParams); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); expect(Transport.listen).toHaveBeenCalled(); - expect(result.eventListenerId).toBe(expectedResponse.eventListenerId); - expect(result.eventListenerResponse).toStrictEqual(expectedResponse.eventListenerResponse); - expect(result.eventListenerSchemaResult.status).toEqual(expectedResponse.eventListenerSchemaResult.status); + expect(result.id).toBe(expectedResponse.id); + expect(result.result).toStrictEqual(expectedResponse.result); }); }); @@ -811,7 +752,7 @@ describe('EventInvocation', () => { eventInvocation = new EventInvocation(); const eventParams = { params: { event: 'mocksdk_mockmodule.onmodulechanged' } }; const result = await eventInvocation.northBoundEventHandling(eventParams); - eventRegistrationID = result.eventListenerId; + eventRegistrationID = result.id; console.log('printing received eventRegistrationId: ' + eventRegistrationID); }); @@ -823,12 +764,11 @@ describe('EventInvocation', () => { test('should return event object with response - single event fired', () => { currentCallback({ foo: 'bar1' }); - const message = { params: { event: eventRegistrationID } }; + const message = { params: { event: 'accessibility.onClosedCaptionsSettingsChanged-6' } }; const expectedResponse = { eventName: 'modulechanged', - eventListenerId: eventRegistrationID, + eventListenerId: 6, eventResponse: { foo: 'bar1' }, - eventSchemaResult: { status: 'PASS', eventSchemaResult: [] }, eventTime: '2023-05-10T14:27:35.806Z', }; result = eventInvocation.getEventResponse(message); @@ -837,7 +777,6 @@ describe('EventInvocation', () => { eventName: expectedResponse.eventName, eventListenerId: expectedResponse.eventListenerId, eventResponse: expectedResponse.eventResponse, - eventSchemaResult: expectedResponse.eventSchemaResult, }); expect(result.eventTime).toBeDefined(); expect(result.eventTime).toBeInstanceOf(Date); @@ -845,12 +784,11 @@ describe('EventInvocation', () => { test('should return event object with last response - multiple events fired', () => { currentCallback({ foo: 'bar2' }); - const message = { params: { event: eventRegistrationID } }; + const message = { params: { event: 'accessibility.onClosedCaptionsSettingsChanged-6' } }; const expectedResponse = { eventName: 'modulechanged', - eventListenerId: eventRegistrationID, + eventListenerId: 6, eventResponse: { foo: 'bar2' }, - eventSchemaResult: { status: 'PASS', eventSchemaResult: [] }, eventTime: '2023-05-10T14:18:18.347Z', }; result = eventInvocation.getEventResponse(message); @@ -859,7 +797,6 @@ describe('EventInvocation', () => { eventName: expectedResponse.eventName, eventListenerId: expectedResponse.eventListenerId, eventResponse: expectedResponse.eventResponse, - eventSchemaResult: expectedResponse.eventSchemaResult, }); expect(result.eventTime).toBeDefined(); expect(result.eventTime).toBeInstanceOf(Date); @@ -895,31 +832,5 @@ describe('EventInvocation', () => { expect(result.error.code).toEqual(expectedResponse.error.code); expect(result.error.message).toBeDefined(); }); - - test('should return failure for schema', async () => { - // register for invalid schema - const message = { params: { event: eventRegistrationID } }; - const expectedResponse = { - eventName: 'modulechanged', - eventListenerId: eventRegistrationID, - eventResponse: true, - eventSchemaResult: { - status: 'FAIL', - eventSchemaResult: 'is not any of "ListenResponse","EventResponse"', - }, - eventTime: '2023-05-11T20:34:05.219Z', - }; - currentCallback(true); - result = eventInvocation.getEventResponse(message); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toMatchObject({ - eventName: expectedResponse.eventName, - eventListenerId: expectedResponse.eventListenerId, - eventResponse: expectedResponse.eventResponse, - eventSchemaResult: expectedResponse.eventSchemaResult, - }); - expect(result.eventTime).toBeDefined(); - expect(result.eventTime).toBeInstanceOf(Date); - }); }); }); diff --git a/test/unit/GetEventResponse.test.js b/test/unit/GetEventResponse.test.js index 51ac53af..defe74d5 100644 --- a/test/unit/GetEventResponse.test.js +++ b/test/unit/GetEventResponse.test.js @@ -55,8 +55,7 @@ describe('GetEventResponse Test Case', () => { const responseString = await getEventResponse.handle(message); console.log(expect.getState().currentTestName + ' : ' + responseString); expect(responseString).toBeTruthy(); - expect(responseString).toContain('report'); - expect(responseString).toEqual('{"report":{"event":"null"}}'); + expect(JSON.parse(responseString).result).toEqual({ event: 'null' }); }); test('Event response- when no event is passed', async () => { @@ -64,7 +63,6 @@ describe('GetEventResponse Test Case', () => { const responseString = await getEventResponse.handle(message); console.log(expect.getState().currentTestName + ' : ' + responseString); expect(responseString).toBeTruthy(); - expect(responseString).toContain('report'); - expect(responseString).toEqual('{"report":{"undefined":"null"}}'); + expect(JSON.parse(responseString).result).toEqual({ undefined: 'null' }); }); }); diff --git a/test/unit/IntentReader.test.js b/test/unit/IntentReader.test.js index 453a2687..e9bd0570 100644 --- a/test/unit/IntentReader.test.js +++ b/test/unit/IntentReader.test.js @@ -64,6 +64,31 @@ jest.mock('@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => { }; }); +jest.mock('@firebolt-js/sdk', () => { + return { + Accessibility: {}, + Account: {}, + Advertising: {}, + Authentication: {}, + Device: {}, + Discovery: {}, + Keyboard: {}, + Lifecycle: { + ready: () => {}, + state: () => { + return 'initializing'; // dummy state value. + }, // returning a Lifecycle.state object + close: () => {}, + finish: () => {}, + }, + Localization: {}, + Metrics: {}, + Profile: {}, + Parameters: {}, + SecondScreen: {}, + }; +}); + const mockFireboltTransportInvoker = { invoke: jest.fn().mockImplementation(() => { return Promise.resolve('success'); diff --git a/test/unit/MethodInvoker.test.js b/test/unit/MethodInvoker.test.js index 01018c77..65f32601 100644 --- a/test/unit/MethodInvoker.test.js +++ b/test/unit/MethodInvoker.test.js @@ -142,11 +142,14 @@ const schemaList = { ], }, { - name: 'mockmodule.mockmethod1', + name: 'mockmodule1.mockmethod1', summary: 'Firebolt OpenRPC schema', params: [], result: { name: 'OpenRPC Schema', + schema: { + type: 'object', + }, }, }, ], @@ -179,6 +182,9 @@ jest.mock('../../src/FireboltExampleInvoker', () => { listen: jest.fn(), clear: jest.fn(), }, + mockmodule2: { + mockmethod2: jest.fn(), + }, }, }; return { @@ -229,52 +235,10 @@ describe('MethodInvoker', () => { action: 'NA', context: { communicationMode: 'Transport' }, }; - const expectedResult = { - method: 'callMethod', - params: [], - responseCode: 1, - apiResponse: { result: 'success', error: null }, - schemaValidationStatus: 'FAIL', - schemaValidationResponse: { - instance: 'success', - schema: { type: 'object' }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [ - { - path: [], - property: 'instance', - message: 'is not of a type(s) object', - schema: { type: 'object' }, - instance: 'success', - name: 'type', - argument: ['object'], - stack: 'instance is not of a type(s) object', - }, - ], - disableFormat: false, - }, - }; - result = await methodInvoker.invoke(MESSAGE_TRANSPORT); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResult); // will be Fail as the schema wont match. Schema expects object, return is string - }); - test('should successfully handle calls with params with communicationMode Transport ', async () => { - process.env.COMMUNICATION_MODE = 'Transport'; - const MESSAGE_TRANSPORT = { - task: 'callMethod', - params: { method: 'mockmodule.mockmethod', methodParams: { value: true } }, - action: 'NA', - context: { communicationMode: 'Transport' }, - }; - const expectedResult = { - method: 'callMethod', - params: [true], - }; + const expectedResult = { id: 1, result: 'success', jsonrpc: '2.0' }; result = await methodInvoker.invoke(MESSAGE_TRANSPORT); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result.params).toEqual(expectedResult.params); // will be Fail as the schema wont match. Schema expects object, return is string + expect(result.result).toEqual(expectedResult.result); // will be Fail as the schema wont match. Schema expects object, return is string }); test('should successfully handle set calls', async () => { process.env.COMMUNICATION_MODE = 'SDK'; @@ -284,483 +248,58 @@ describe('MethodInvoker', () => { action: 'NA', context: { communicationMode: 'SDK' }, }; - const expectedResponse = { - method: 'callMethod', - params: [true], - responseCode: 0, - apiResponse: { result: 'success', error: null }, - schemaValidationStatus: 'PASS', - schemaValidationResponse: { - instance: 'success', - schema: { type: 'string' }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }, - }; + const expectedResponse = { id: 1, result: 'success', jsonrpc: '2.0' }; result = await methodInvoker.invoke(message); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); // will return PASS as module and method exist as well as schema validation passes. + expect(result.result).toEqual(expectedResponse.result); // will return PASS as module and method exist as well as schema validation passes. }); - test('should successfully handle calls with params as object', async () => { + test('should return wrong method name when method not in sdk', async () => { process.env.COMMUNICATION_MODE = 'SDK'; const message = { task: 'callMethod', params: { - method: 'mocksdk_mockmodule.mockmethod', - methodParams: { firstParam: true, secondParam: 'somestring' }, + method: 'mocksdk_mockmodule.invalidMethod', }, action: 'NA', context: { communicationMode: 'SDK' }, }; const expectedResponse = { - method: 'callMethod', - params: [true, 'somestring'], + id: 1, + error: { code: -32601, message: 'Wrong Method Name' }, + jsonrpc: '2.0', }; result = await methodInvoker.invoke(message); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result.method).toEqual(expectedResponse.method); - expect(result.params).toEqual(expectedResponse.params); + expect(result.error).toEqual(expectedResponse.error); // will return result will be null. }); - test('should return error on error during processing', async () => { - process.env.COMMUNICATION_MODE = 'SDK'; - const message = { + test('validate MethodInvoker method which present in OPEN RPC but not imported to moduleMap/firebolt invoker', async () => { + process.env.COMMUNICATION_MODE = 'Transport'; + const MESSAGE_TRANSPORT = { task: 'callMethod', - params: { - method: 'mockmodule.mockmethod1', - }, + params: { method: 'mockmodule1.mockmethod1' }, action: 'NA', - context: { communicationMode: 'SDK' }, - }; - const expectedResponse = { - method: 'callMethod', - params: [], - responseCode: 1, - apiResponse: { - result: null, - error: { code: 'FCAError', message: 'Expected `schema` to be an object or boolean' }, - }, - schemaValidationStatus: 'FAIL', - schemaValidationResponse: { - instance: { code: 'FCAError', message: 'Expected `schema` to be an object or boolean' }, - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [ - { - path: [], - property: 'instance', - message: 'is not exactly one from [subschema 0],[subschema 1]', - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - instance: { - code: 'FCAError', - message: 'Expected `schema` to be an object or boolean', - }, - name: 'oneOf', - argument: ['[subschema 0]', '[subschema 1]'], - stack: 'instance is not exactly one from [subschema 0],[subschema 1]', - }, - ], - disableFormat: false, - }, + context: { communicationMode: 'Transport' }, }; - result = await methodInvoker.invoke(message); + const expectedResult = { id: 1, result: 'success', jsonrpc: '2.0' }; + result = await methodInvoker.invoke(MESSAGE_TRANSPORT); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); // will return result will be null. + expect(result.result).toEqual(expectedResult.result); // will be Fail as the schema wont match. Schema expects object, return is string }); - test('should return wrong method name when method not in sdk', async () => { - process.env.COMMUNICATION_MODE = 'SDK'; - const message = { + test('validate MethodInvoker method which is not present in OPEN RPC but present in moduleMap/firebolt invoker', async () => { + process.env.COMMUNICATION_MODE = 'Transport'; + const MESSAGE_TRANSPORT = { task: 'callMethod', - params: { - method: 'mocksdk_mockmodule.invalidMethod', - }, + params: { method: 'mockmodule2.mockmethod2' }, action: 'NA', - context: { communicationMode: 'SDK' }, - }; - - const expectedResponse = { - method: 'callMethod', - params: [], - responseCode: 0, - apiResponse: { result: null, error: { code: -32601, message: 'Wrong Method Name' } }, - schemaValidationStatus: 'PASS', - schemaValidationResponse: { - instance: { code: -32601, message: 'Wrong Method Name' }, - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }, - }; - result = await methodInvoker.invoke(message); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); // will return result will be null. - }); - }); - - describe('formatResult', () => { - let task; - let response; - let err; - let schemaValidationResult; - let params; - let schemaMap; - let expectedResponse; - - beforeEach(() => { - task = 'mockTask'; - response = null; - err = null; - schemaValidationResult = null; - params = { mockParams: 'mockValue' }; - schemaMap = { - type: 'string', - }; - expectedResponse = null; - }); - - test('should return status code 3 with Schema Validation status PASS - err in pending list, valid format', () => { - // testing for method not found - err = { code: -123, message: 'Method not found' }; - let expectedResponse = { - method: task, - params: params, - responseCode: 3, - apiResponse: { result: response, error: err }, - schemaValidationStatus: 'PASS', - schemaValidationResponse: { - instance: err, - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }, - }; - let result = methodInvoker.formatResult(task, response, err, schemaValidationResult, params, schemaMap); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); - - // testing for Method Not Implemented - err = { code: -123, message: 'Method Not Implemented' }; - expectedResponse = { - method: task, - params: params, - responseCode: 3, - apiResponse: { result: response, error: err }, - schemaValidationStatus: 'PASS', - schemaValidationResponse: { - instance: err, - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }, - }; - result = methodInvoker.formatResult(task, response, err, schemaValidationResult, params, schemaMap); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); - }); - - test('should return status code 3 with Schema Validation status FAIL - err in pending list, invalid format', () => { - // testing for error not in expected format - err = { code: 'mockError', message: 'Method Not Implemented' }; - expectedResponse = { - method: task, - params: params, - responseCode: 3, - apiResponse: { result: response, error: err }, - schemaValidationStatus: 'FAIL', - schemaValidationResponse: { - instance: err, - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [ - { - path: [], - property: 'instance', - message: 'is not exactly one from [subschema 0],[subschema 1]', - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - instance: { code: 'mockError', message: 'Method Not Implemented' }, - name: 'oneOf', - argument: ['[subschema 0]', '[subschema 1]'], - stack: 'instance is not exactly one from [subschema 0],[subschema 1]', - }, - ], - disableFormat: false, - }, - }; - result = methodInvoker.formatResult(task, response, err, schemaValidationResult, params, schemaMap); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); - }); - - test('should return status code 1 with Schema Validation status FAIL - err invalid format', () => { - // testing for error not in expected format - err = { code: 'mockError', message: 'some error' }; - expectedResponse = { - method: task, - params: params, - responseCode: 1, - apiResponse: { result: response, error: err }, - schemaValidationStatus: 'FAIL', - schemaValidationResponse: { - instance: err, - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [ - { - path: [], - property: 'instance', - message: 'is not exactly one from [subschema 0],[subschema 1]', - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - instance: { code: 'mockError', message: 'some error' }, - name: 'oneOf', - argument: ['[subschema 0]', '[subschema 1]'], - stack: 'instance is not exactly one from [subschema 0],[subschema 1]', - }, - ], - disableFormat: false, - }, - }; - result = methodInvoker.formatResult(task, response, err, schemaValidationResult, params, schemaMap); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); - }); - - test('should return status code 0 with Schema Validation status PASS - err valid format', () => { - // testing for error not in expected format - err = { code: -123, message: 'some error' }; - expectedResponse = { - method: task, - params: params, - responseCode: 0, - apiResponse: { result: response, error: err }, - schemaValidationStatus: 'PASS', - schemaValidationResponse: { - instance: err, - schema: { - oneOf: [ - { - type: 'object', - properties: { code: { type: 'number' }, message: { type: 'string' } }, - required: ['code', 'message'], - }, - { type: 'string' }, - ], - }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }, - }; - result = methodInvoker.formatResult(task, response, err, schemaValidationResult, params, schemaMap); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); - }); - - test('should return status code 0 with Schema Validation status PASS - valid response format', () => { - // testing for method not found - response = 'expectedResponse'; - schemaValidationResult = { - instance: response, - schema: { type: 'string' }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }; - const expectedResponse = { - method: task, - params: params, - responseCode: 0, - apiResponse: { result: response, error: err }, - schemaValidationStatus: 'PASS', - schemaValidationResponse: schemaValidationResult, - }; - const result = methodInvoker.formatResult(task, response, err, schemaValidationResult, params, schemaMap); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); - }); - - test('should return status code 0 with Schema Validation status PASS - valid null response', () => { - // testing for error not in expected format - response = null; - schemaMap = { - type: null, - }; - schemaValidationResult = { - instance: response, - schema: schemaMap, - options: {}, - path: [], - propertyPath: 'instance', - errors: [], - disableFormat: false, - }; - expectedResponse = { - method: task, - params: params, - responseCode: 0, - apiResponse: { result: response, error: err }, - schemaValidationStatus: 'PASS', - schemaValidationResponse: schemaValidationResult, - }; - result = methodInvoker.formatResult(task, response, err, schemaValidationResult, params, schemaMap); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); - }); - - test('should return status code 2 with Schema Validation status SKIPPED - response is undefined but schema is not null', () => { - // testing for error not in expected format - response = undefined; - const expectedResponse = { - method: task, - params: params, - responseCode: 2, - apiResponse: { result: null, error: 'undefined' }, - schemaValidationStatus: 'SKIPPED', - schemaValidationResponse: null, - }; - result = methodInvoker.formatResult(task, response, err, schemaValidationResult, params, schemaMap); - console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); - }); - - test('should return status code 1 with Schema Validation status FAIL - response invalid format', () => { - // testing for error not in expected format - response = { message: 'some response' }; - schemaValidationResult = { - instance: response, - schema: { type: 'string' }, - options: {}, - path: [], - propertyPath: 'instance', - errors: [ - { - path: ['response'], - property: 'response', - message: 'is not of a type(s) string', - schema: { type: 'string' }, - instance: response, - name: 'type', - argument: ['string'], - stack: 'instance is not of a type(s) string', - }, - ], - disableFormat: false, - }; - expectedResponse = { - method: task, - params: params, - responseCode: 1, - apiResponse: { result: response, error: null }, - schemaValidationStatus: 'FAIL', - schemaValidationResponse: schemaValidationResult, + context: { communicationMode: 'Transport' }, }; - result = methodInvoker.formatResult(task, response, err, schemaValidationResult, params, schemaMap); + const expectedResult = { id: 1, result: 'success', jsonrpc: '2.0' }; + result = await methodInvoker.invoke(MESSAGE_TRANSPORT); console.log(expect.getState().currentTestName + ' : ' + JSON.stringify(result)); - expect(result).toEqual(expectedResponse); + expect(result.result).toEqual(expectedResult.result); // will be Fail as the schema wont match. Schema expects object, return is string }); }); -}); +}); \ No newline at end of file diff --git a/test/unit/RegisterEventHandler.test.js b/test/unit/RegisterEventHandler.test.js index 1da548bd..bbe4700d 100644 --- a/test/unit/RegisterEventHandler.test.js +++ b/test/unit/RegisterEventHandler.test.js @@ -28,16 +28,12 @@ jest.mock('../../src/EventInvocation', () => { return { northBoundEventHandling: (message) => { return { - eventName: message.params.event, - eventListenerId: message.params.event + '-146', - eventListenerResponse: { - listenerResponse: 146, - error: null, - }, - eventListenerSchemaResult: { - status: 'PASS', - eventSchemaResult: {}, + jsonrpc: '2.0', + result: { + listening: true, + event: message.params.event, }, + id: 1, }; }, }; @@ -64,9 +60,8 @@ describe('RegisterEventHandler', () => { const responseString = await registerEventHandler.handle(message); console.log(expect.getState().currentTestName + ' : ' + responseString); expect(responseString).toBeTruthy(); - expect(responseString).toContain('report'); const response = JSON.parse(responseString); - expect(response.report.eventName).toEqual(message.params.event); + expect(response.result.event).toEqual(message.params.event); expect(process.env.COMMUNICATION_MODE).toEqual(message.context.communicationMode); }); test('validate sdktype is handled correctly - MANAGE', async () => { @@ -79,9 +74,8 @@ describe('RegisterEventHandler', () => { const responseString = await registerEventHandler.handle(message); console.log(expect.getState().currentTestName + ' : ' + responseString); expect(responseString).toBeTruthy(); - expect(responseString).toContain('report'); const response = JSON.parse(responseString); - expect(response.report.eventName).toEqual(message.params.event); + expect(response.result.event).toEqual(message.params.event); }); test('validate sdktype is handled correctly - no match found', async () => { const message = { @@ -93,10 +87,9 @@ describe('RegisterEventHandler', () => { const responseString = await registerEventHandler.handle(message); console.log(expect.getState().currentTestName + ' : ' + responseString); expect(responseString).toBeTruthy(); - expect(responseString).toContain('report'); const response = JSON.parse(responseString); - expect(response.report.error.code).toEqual('FCA Error'); - expect(response.report.error.message).toEqual("Not supported. sdkType 'extension' not in ['core','manage']"); + expect(response.error.code).toEqual('FCA Error'); + expect(response.error.message).toEqual("Not supported. sdkType 'extension' not in ['core','manage']"); }); }); }); diff --git a/test/unit/test_runner.test.js b/test/unit/test_runner.test.js index f59d20c6..c871d7cc 100644 --- a/test/unit/test_runner.test.js +++ b/test/unit/test_runner.test.js @@ -29,14 +29,14 @@ const Validator = require('jsonschema').Validator; let MOCK_OPEN_RPC_DOC = { methods: [ { - name: 'account.id', + name: 'Account.id', summary: 'Firebolt OpenRPC schema', params: [], result: { name: 'id', summary: 'the id', schema: { - type: 'object', + type: 'string', }, }, examples: [ @@ -51,7 +51,7 @@ let MOCK_OPEN_RPC_DOC = { ], }, { - name: 'account.uid', + name: 'Account.uid', summary: 'Gets a unique id for the current app & account', params: [], result: { @@ -72,8 +72,328 @@ let MOCK_OPEN_RPC_DOC = { }, ], }, + { + name: 'Device.id', + summary: 'Get the platform back-office device identifier', + params: [], + result: { + name: 'id', + summary: 'the id', + schema: { + type: 'string', + }, + }, + examples: [ + { + name: 'Default Example', + params: [], + result: { + name: 'Default Result', + value: '123', + }, + }, + ], + }, + { + name: 'Device.platform', + summary: 'Get the platform ID for this device', + params: [], + result: { + name: 'platformId', + summary: 'the platform ID', + schema: { + type: 'string', + }, + }, + examples: [ + { + name: 'Getting the platform ID', + params: [], + result: { + name: 'Default Result', + value: 'WPE', + }, + }, + ], + }, + { + name: 'Device.uid', + summary: 'Gets a unique id for the current app & device', + params: [], + result: { + name: 'uniqueId', + summary: 'a unique ID', + schema: { + type: 'string', + }, + }, + examples: [ + { + name: 'Getting the unique ID', + params: [], + result: { + name: 'Default Result', + value: 'ee6723b8-7ab3-462c-8d93-dbf61227998e', + }, + }, + ], + }, + { + name: 'Device.distributor', + summary: 'Get the distributor ID for this device', + params: [], + result: { + name: 'distributorId', + summary: 'the distributor ID', + schema: { + type: 'string', + }, + }, + examples: [ + { + name: 'Getting the distributor ID', + params: [], + result: { + name: 'Default Result', + value: 'Company', + }, + }, + ], + }, + { + name: 'Device.type', + summary: 'Get the device type', + params: [], + result: { + name: 'deviceType', + summary: 'the device type', + schema: { + type: 'string', + }, + }, + examples: [ + { + name: 'Getting the device type', + params: [], + result: { + name: 'Default Result', + value: 'STB', + }, + }, + ], + }, + { + name: 'Device.model', + summary: 'Get the device model', + params: [], + result: { + name: 'model', + summary: 'the device model', + schema: { + type: 'string', + }, + }, + examples: [ + { + name: 'Getting the device model', + params: [], + result: { + name: 'Default Result', + value: 'xi6', + }, + }, + ], + }, + { + name: 'Device.sku', + summary: 'Get the device sku', + params: [], + result: { + name: 'sku', + summary: 'the device sku', + schema: { + type: 'string', + }, + }, + examples: [ + { + name: 'Getting the device sku', + params: [], + result: { + name: 'Default Result', + value: 'AX061AEI', + }, + }, + ], + }, + { + name: 'Device.make', + summary: 'Get the device make', + params: [], + result: { + name: 'make', + summary: 'the device make', + schema: { + type: 'string', + }, + }, + }, + { + name: 'Device.hdcp', + summary: 'Get the supported HDCP profiles', + params: [], + tags: [ + { + name: 'property:readonly', + }, + { + name: 'capabilities', + 'x-uses': ['xrn:firebolt:capability:device:info'], + }, + ], + result: { + name: 'supportedHdcpProfiles', + summary: 'the supported HDCP profiles', + schema: { + type: 'object', + additionalProperties: { + type: 'boolean', + }, + }, + }, + examples: [ + { + name: 'Getting the supported HDCP profiles', + params: [], + result: { + name: 'Default Result', + value: { + 'hdcp1.4': true, + 'hdcp2.2': true, + }, + }, + }, + ], + }, + { + name: 'Accessibility.onClosedCaptionsSettingsChanged', + summary: "Get the user's preferred closed-captions settings", + params: [ + { + name: 'listen', + required: true, + schema: { + type: 'boolean', + }, + }, + ], + tags: [ + { + name: 'subscriber', + 'x-subscriber-for': 'Accessibility.closedCaptionsSettings', + }, + { + name: 'event', + 'x-alternative': 'closedCaptionsSettings', + }, + { + name: 'capabilities', + 'x-uses': ['xrn:firebolt:capability:accessibility:closedcaptions'], + }, + ], + result: { + name: 'closedCaptionsSettings', + summary: 'the closed captions settings', + schema: { + anyOf: [ + { + type: 'object', + required: ['event', 'listening'], + properties: { + event: { + type: 'string', + pattern: '[a-zA-Z]+\\.on[A-Z][a-zA-Z]+', + }, + listening: { + type: 'boolean', + }, + }, + additionalProperties: false, + }, + { + type: 'object', + required: ['enabled', 'styles'], + properties: { + enabled: { + type: 'boolean', + description: 'Whether or not closed-captions should be enabled by default', + }, + styles: { + type: 'object', + description: 'The default styles to use when displaying closed-captions', + }, + preferredLanguages: { + type: 'array', + items: { + type: 'string', + pattern: '^[a-z]{3}$', + }, + }, + }, + }, + ], + }, + }, + examples: [ + { + name: 'Getting the closed captions settings', + params: [ + { + name: 'listen', + value: true, + }, + ], + result: { + name: 'settings', + value: { + enabled: true, + styles: { + fontFamily: 'monospaced_sanserif', + fontSize: 1, + fontColor: '#ffffff', + fontEdge: 'none', + fontEdgeColor: '#7F7F7F', + fontOpacity: 100, + backgroundColor: '#000000', + backgroundOpacity: 100, + textAlign: 'center', + textAlignVertical: 'middle', + windowColor: 'white', + windowOpacity: 50, + }, + preferredLanguages: ['eng', 'spa'], + }, + }, + }, + ], + }, ], }; + +const mockResponses = { + 'Account.id': '123', + 'Account.uid': undefined, + 'Device.platform': { error: 'capability xrn:firebolt:capability:platformn is not supported' }, + 'Device.uid': { error: { code: -32601, message: 'Method not found' } }, + 'Device.distributor': { code: -50100, message: 'capability xrn:firebolt:capability:token:session is not supported' }, + 'Device.type': { error: { message: 'capability xrn:firebolt:capability:Localization.locality is not supported' } }, + 'Device.model': { error: { code: -32601, message: 'capability xrn:firebolt:capability:platformn is not supported' } }, + 'Device.sku': { error: { code: -32601, message: 'Method not found' } }, + 'Device.make': 'Arris', + 'De vice.hdcp': { 'hdcp1.4': true, 'hdcp2.2': true }, + 'Accessibility.onClosedCaptionsSettingsChanged': 'Successful accessibility.listen(closedCaptionsSettingsChanged)', +}; + const EXTERNAL_SDK_MOCK_OPEN_RPC_DOC = { methods: [ { @@ -122,7 +442,7 @@ const EXTERNAL_SDK_MOCK_OPEN_RPC_DOC = { }, ], }; -const MOCK_OPEN_RPC_RESPONSE = { id: 18, result: {}, jsonrpc: '2.0' }; + /** * This is the definition of the structure used by Validation view * to create the menu and also to show the result. @@ -152,83 +472,91 @@ const CUSTOM_REPORT_STRUCTURE_SCHEMA = { }, }; -jest.mock('@apidevtools/json-schema-ref-parser', () => { - return { - dereference: () => { - return new Promise((resolve, reject) => { - if (!mockShouldDereferencerFail) { - resolve(MOCK_OPEN_RPC_DOC); - } else { - reject(new Error('Dereferencer Failure')); - } - }); - }, - }; -}); +jest.mock('@apidevtools/json-schema-ref-parser', () => ({ + dereference: () => + new Promise((resolve, reject) => { + if (!mockShouldDereferencerFail) { + resolve(MOCK_OPEN_RPC_DOC); + } else { + reject(new Error('Dereferencer Failure')); + } + }), +})); + /** * mock object used to emulate the response from * FireBoltExampleInvoker */ const mockFireboltExampleInvoker = { - invoke: () => {}, + invoke: jest.fn((sdk, methodName, params) => { + return returnMockResponse(methodName); + }), }; -jest.mock('../../src/FireboltExampleInvoker', () => { - return { - get: () => { - return mockFireboltExampleInvoker; - }, - }; -}); -jest.mock('@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => { - return { - send: () => { - return {}; - }, - }; -}); -jest.mock('../../src/FireboltTransportInvoker', () => { - return { - get: () => { - return mockFireboltTransportInvoker; - }, - }; -}); -jest.mock('@firebolt-js/sdk', () => { - return { - Accessibility: {}, - Account: {}, - Advertising: {}, - Authentication: {}, - Device: {}, - Discovery: {}, - Keyboard: {}, - Lifecycle: { - ready: () => {}, - state: () => { - return 'initializing'; // dummy state value. - }, // returning a Lifecycle.state object - close: () => {}, - finish: () => {}, - }, - Localization: {}, - Metrics: {}, - Profile: {}, - Parameters: {}, - SecondScreen: {}, - }; -}); -jest.mock('../../src/pubsub/handlers/RegisterProviderHandler', () => { - return jest.fn().mockImplementation(() => ({ + +const mockFireboltTransportInvoker = { + invoke: jest.fn((methodName, params) => { + return returnMockResponse(methodName); + }), +}; + +function returnMockResponse(methodName) { + return new Promise((resolve, reject) => { + if (mockResponses.hasOwnProperty(methodName)) { + const response = mockResponses[methodName]; + if (response && response.error) { + reject(response.error); + } else { + resolve(response); + } + } else { + resolve({}); + } + }); +} + +jest.mock('../../src/FireboltExampleInvoker', () => ({ + get: () => mockFireboltExampleInvoker, +})); + +jest.mock('@firebolt-js/sdk/dist/lib/Transport/index.mjs', () => ({ + send: jest.fn().mockReturnValue({}), +})); + +jest.mock('../../src/FireboltTransportInvoker', () => ({ + get: () => mockFireboltTransportInvoker, +})); + +jest.mock('@firebolt-js/sdk', () => ({ + Accessibility: {}, + Account: {}, + Advertising: {}, + Authentication: {}, + Device: {}, + Discovery: {}, + Keyboard: {}, + Lifecycle: { + ready: jest.fn(), + state: jest.fn().mockReturnValue('initializing'), + close: jest.fn(), + finish: jest.fn(), + }, + Localization: {}, + Metrics: {}, + Profile: {}, + Parameters: {}, + SecondScreen: {}, +})); + +jest.mock('../../src/pubsub/handlers/RegisterProviderHandler', () => + jest.fn().mockImplementation(() => ({ handle: jest.fn().mockResolvedValue(JSON.stringify({ report: 'registered' })), - })); -}); -jest.mock('../../src/Toast', () => { - const eventEmitter = { - emit: jest.fn(), - }; + })) +); +jest.mock('../../src/Toast', () => { + const eventEmitter = { emit: jest.fn() }; return { - eventEmitter: eventEmitter, + eventEmitter, showToast: (toastMessage, toastState, toastRef) => { eventEmitter.emit('showToast', toastMessage, toastState, toastRef); }, @@ -241,138 +569,216 @@ const mockvalidationViewObj = { jest.mock('../../src/utils/Utils', () => ({ ...jest.requireActual('../../src/utils/Utils'), - pushReportToS3: () => { - return 'restApiUrl'; - }, - censorData: () => { - return 'censoredResponse'; - }, - dereferenceOpenRPC: (mode) => { - if (mode == 'externalsdk') { + pushReportToS3: jest.fn().mockReturnValue('restApiUrl'), + censorData: jest.fn((method, response) => { + return response; + }), + dereferenceOpenRPC: jest.fn((mode) => { + if (mode === 'externalsdk') { return [EXTERNAL_SDK_MOCK_OPEN_RPC_DOC, mode.toLowerCase()]; - } else if (mode == 'core' || mode == 'manage') { + } else if (mode === 'core' || mode === 'manage') { return [MOCK_OPEN_RPC_DOC, mode.toLowerCase()]; } - }, + }), +})); + +jest.mock('lodash', () => ({ + cloneDeep: jest.fn((value) => value), })); let mockShouldDereferencerFail = false; let runner; let result; const navigation = ''; +const INCLUDE_EVENT_METHODS = []; + +jest.mock('../../src/MethodFilters', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(() => ({ + isExceptionMethod: jest.fn((methodName) => { + const exceptionMethods = ['Device.distributor', 'Device.type', 'Device.model', 'Device.sku']; + return exceptionMethods.includes(methodName); + }), + isMethodToBeExcluded: jest.fn((methodObject) => { + const excludedMethodsList = ['Device.hdcp']; + return excludedMethodsList.includes(methodObject.name); + }), + isRpcMethod: jest.fn(), + isSubscribeMethod: jest.fn(), + isSetMethod: jest.fn(), + shouldExcludeExample: jest.fn(), + isEventMethod: jest.fn((method) => { + let isEvent = false; + if (method.tags && INCLUDE_EVENT_METHODS.indexOf(method.name) === -1) { + method.tags.forEach((tag) => { + if (tag.name && tag.name === 'event') { + isEvent = true; + } + }); + } + return isEvent; + }), + })), +})); describe('Test_Runner test cases', () => { beforeEach(() => { runner = new Test_Runner(); - (runner.reportGenenration = jest.fn().mockImplementationOnce(() => { - return new Promise((resolve) => { - resolve(''); - }); - })), - (runner.invokeLifecycleAPI = jest.fn().mockImplementationOnce((tempParams) => { - if (tempParams.methodName == CONSTANTS.LIFECYCLE_METHOD_LIST[1]) { - return 'initializing'; - } else { - const mockLifecycleHistoryget = { _history: [{ someKey: 'someValue' }] }; - return mockLifecycleHistoryget; - } - })); + runner.reportGenenration = jest.fn().mockResolvedValue(''); + runner.invokeLifecycleAPI = jest.fn().mockImplementation((tempParams) => { + if (tempParams.methodName === CONSTANTS.LIFECYCLE_METHOD_LIST[1]) { + return 'initializing'; + } else { + return { _history: [{ someKey: 'someValue' }] }; + } + }); }); + describe('northBoundSchemaValidationAndReportGeneration Scenarios', () => { - test('Validate northBoundSchemaValidationAndReportGeneration(SDK) when OPEN RPC dereferece call fails', async () => { + test('should return empty result when dereference call fails for SDK', async () => { mockShouldDereferencerFail = true; - result = await runner.northBoundSchemaValidationAndReportGeneration('SDK', navigation, mockvalidationViewObj); + result = await runner.northBoundSchemaValidationAndReportGeneration('SDK'); /** when the dereference fails it should not execute any api and the result list will have 0 elements */ expect(result.length).toEqual(0); }); - test('Validate northBoundSchemaValidationAndReportGeneration(CORE) with example with valid response from FireboltExapmpleInvoker', async () => { - mockShouldDereferencerFail = false; - // Mock a valid response coming back from the Firebolt Invoker - mockFireboltExampleInvoker.invoke = () => Promise.resolve(MOCK_OPEN_RPC_RESPONSE); - result = await runner.northBoundSchemaValidationAndReportGeneration(CONSTANTS.CORE); - /** - * Since the mocked OPEN_RPC has 2 documents we will have 2 results - */ - expect(result.length).toEqual(2); - const v = new Validator(); - const schemaMapResult = v.validate(result[0], CUSTOM_REPORT_STRUCTURE_SCHEMA); - // This would make sure that the result json that is created is in valid strcuture - // we are not bothered about the value in the json. - expect(schemaMapResult.errors.length).toEqual(0); - /** - * Validate if the response title is populated correctly - */ - expect(result[1].fullTitle).toEqual('account.uid.Getting the unique ID'); - expect(result[0].state).toEqual('passed'); - }); - test('Validate northBoundSchemaValidationAndReportGeneration(CORE) with a invalid schema response coming back from FireboltExampleInvoker', async () => { - mockShouldDereferencerFail = false; - // Mock an invalid schema response coming back from the Firebolt Invoker - mockFireboltExampleInvoker.invoke = () => Promise.resolve(null); + describe('northBoundSchemaValidationAndReportGeneration Scenarios for CORE', () => { + beforeAll(async () => { + result = await runner.northBoundSchemaValidationAndReportGeneration(CONSTANTS.CORE); + }); + test('should return valid response for Account.id API', async () => { + const extractedResult = result.find((obj) => obj.title === 'Account.id'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Passed'); + expect(extractedResult.code.Response.result).toBeDefined(); + }); - result = await runner.northBoundSchemaValidationAndReportGeneration(CONSTANTS.CORE); - /** - * Since the mocked OPEN_RPC has 2 documents we will have 2 results - */ - expect(result.length).toEqual(2); - // Both results should have resulted in schema validation failure - expect(result[0].state).toEqual('failed'); + test('should fail for Account.uid API due to undefined response', async () => { + const extractedResult = result.find((obj) => obj.title === 'Account.uid'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Failed'); + expect(extractedResult.code.Response).toBeNull(); + expect(extractedResult.code.Message).toContain('No result or error in response.'); + }); + + test('should handle different type of response for Device.id API', async () => { + const extractedResult = result.find((obj) => obj.title === 'Device.id'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Failed'); + expect(extractedResult.code.Response.result).toBeDefined(); + expect(extractedResult.code.Message).toContain('instance.result is not of a type(s) string'); + }); + + test('should handle unexpected error for Device.platform API', async () => { + const extractedResult = result.find((obj) => obj.title === 'Device.platform'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Failed'); + expect(extractedResult.code.Response.error).toBeDefined(); + expect(extractedResult.code.Message).toContain('Unexpected error encountered in the response'); + }); + + test('should handle unexpected error: "method not implemented" for Device.uid API', async () => { + const extractedResult = result.find((obj) => obj.title === 'Device.uid'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Failed'); + expect(extractedResult.code.Response.error).toBeDefined(); + expect(extractedResult.code.Message).toContain('Method not implemented by platform'); + }); + + test('should handle expecting error but received result for Device.distributor API', async () => { + const extractedResult = result.find((obj) => obj.title === 'Device.distributor'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Failed'); + expect(extractedResult.code.Response.result).toBeDefined(); + expect(extractedResult.code.Message).toContain('Expected error, received result'); + }); + + test('should handle expecting error but incorrect error format for Device.type API', async () => { + const extractedResult = result.find((obj) => obj.title === 'Device.type'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Failed'); + expect(extractedResult.code.Response.error).toBeDefined(); + expect(extractedResult.code.Message).toContain('Expected error, incorrect error format'); + }); + + test('should handle expected error but received error for Device.model API', async () => { + const extractedResult = result.find((obj) => obj.title === 'Device.model'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Passed'); + expect(extractedResult.code.Response.error).toBeDefined(); + expect(extractedResult.code.Message).toContain('Expected error, received error'); + }); + + test('should handle method not implemented error for Device.sku API', async () => { + const extractedResult = result.find((obj) => obj.title === 'Device.sku'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Passed'); + expect(extractedResult.code.Response.error).toBeDefined(); + expect(extractedResult.code.Message).toContain('Method not implemented by platform'); + }); + + test('should skip validation for Device.make API due to missing example', async () => { + const extractedResult = result.find((obj) => obj.title === 'Device.make'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Skipped'); + expect(extractedResult.code.Response).toBeNull(); + expect(extractedResult.code.Message).toContain('Could not find an example for Device.make'); + }); + + test('should return valid response for Accessibility.onClosedCaptionsSettingsChanged Event', async () => { + const extractedResult = result.find((obj) => obj.title === 'Accessibility.onClosedCaptionsSettingsChanged'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Passed'); + expect(extractedResult.code.Response.result).toBeDefined(); + }); }); - test('Validate northBoundSchemaValidationAndReportGeneration when invalid mode is passed', async () => { - /** when the invalid mode is passed dereference fails it should not execute any api */ + + test('should return error when invalid mode is passed', async () => { mockShouldDereferencerFail = false; result = await runner.northBoundSchemaValidationAndReportGeneration('undefined', navigation, mockvalidationViewObj); expect(result.error).toEqual(CONSTANTS.NOTPERFORMED); }); - test('Validate northBoundSchemaValidationAndReportGeneration(CORE) with no example with valid response from FireboltExapmpleInvoker', async () => { - mockShouldDereferencerFail = false; - // Mock a valid response coming back from the Firebolt Invoker - // Overriding MOCK_OPEN_RPC_DOC value for device module test - + test('should handle when mode as passes as an arry', async () => { MOCK_OPEN_RPC_DOC = { methods: [ { - name: 'device.uid', + name: 'Account.id', summary: 'Firebolt OpenRPC schema', params: [], result: { - name: 'OpenRPC Schema', + name: 'id', + summary: 'the id', schema: { - type: 'object', + type: 'string', }, }, + examples: [ + { + name: 'Default Example', + params: [], + result: { + name: 'Default Result', + value: '123', + }, + }, + ], }, ], }; - mockFireboltExampleInvoker.invoke = () => Promise.resolve(MOCK_OPEN_RPC_RESPONSE); - - result = await runner.northBoundSchemaValidationAndReportGeneration(CONSTANTS.CORE); - /** - * Since the mocked OPEN_RPC has 2 documents we will have 2 results - */ - expect(result.length).toEqual(1); - const v = new Validator(); - const schemaMapResult = v.validate(result[0], CUSTOM_REPORT_STRUCTURE_SCHEMA); - // This would make sure that the result json that is created is in valid strcuture - // we are not bothered about the value in the json. - expect(schemaMapResult.errors.length).toEqual(0); - /** - * Validate if the response title is populated correctly - */ - expect(result[0].fullTitle).toEqual('device.uid'); - expect(result[0].code).toContain('Could not find an example for device.uid'); + result = await runner.northBoundSchemaValidationAndReportGeneration([CONSTANTS.CORE]); + const extractedResult = result.find((obj) => obj.title === 'Account.id'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Passed'); + expect(extractedResult.code.Response.result).toBeDefined(); }); - test('Validate northBoundSchemaValidationAndReportGeneration when module is device (schema and content validation from library)', async () => { - /** when the module is device, content and schema validation would be done externally */ - mockShouldDereferencerFail = false; - // Overriding MOCK_OPEN_RPC_DOC value for device module test + + test('should handle when communication mode is Transport', async () => { MOCK_OPEN_RPC_DOC = { methods: [ { - name: 'device.id', - summary: 'Get the platform back-office device identifier', + name: 'Account.id', + summary: 'Firebolt OpenRPC schema', params: [], result: { name: 'id', @@ -394,41 +800,42 @@ describe('Test_Runner test cases', () => { }, ], }; - mockFireboltExampleInvoker.invoke = () => Promise.resolve(MOCK_OPEN_RPC_RESPONSE); - result = await runner.northBoundSchemaValidationAndReportGeneration(CONSTANTS.CORE, navigation, mockvalidationViewObj); - const parsedCode = JSON.parse(result[0].code); - // Schema validation is expected to fail as MOCK_OPEN_RPC_RESPONSE is not in the expected schema for device module - expect(parsedCode.Schema).toEqual('Failed'); - // Content validation will be skipped when schema validation fails - expect(parsedCode.Content).toEqual('Pending'); - - // Reverting MOCK_OPEN_RPC_DOC to previous value + process.env.COMMUNICATION_MODE = 'Transport'; + result = await runner.northBoundSchemaValidationAndReportGeneration([CONSTANTS.CORE]); + const extractedResult = result.find((obj) => obj.title === 'Account.id'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Passed'); + expect(extractedResult.code.Response.result).toBeDefined(); + }); + + test('should handle when schema is missing from openRpc', async () => { MOCK_OPEN_RPC_DOC = { methods: [ { - name: 'rpc.discover', + name: 'Account.id', summary: 'Firebolt OpenRPC schema', params: [], result: { - name: 'OpenRPC Schema', - schema: { - type: 'object', - }, + name: 'id', + summary: 'the id', }, - }, - { - name: 'account.id', - summary: 'Firebolt OpenRPC schema', - params: [], - result: { - name: 'OpenRPC Schema', - schema: { - type: 'object', + examples: [ + { + name: 'Default Example', + params: [], + result: { + name: 'Default Result', + value: '123', + }, }, - }, + ], }, ], }; + result = await runner.northBoundSchemaValidationAndReportGeneration([CONSTANTS.CORE]); + const extractedResult = result.find((obj) => obj.title === 'Account.id'); + extractedResult.code = JSON.parse(extractedResult.code); + expect(extractedResult.code['Schema Validation']).toEqual('Skipped'); }); }); describe('UUID Generation Validation', () => { @@ -468,7 +875,6 @@ describe('Test_Runner test cases', () => { mode: 'Lifecycle.validation', methodName: CONSTANTS.LIFECYCLE_METHOD_LIST[1], }; - jest.spyOn(runner, 'schemaValidation').mockImplementation(() => 'Passed'); const response = await runner.invokeLifecycleAPI(tempParams); expect(response).not.toBe(undefined); /* diff --git a/webpack.dev.js b/webpack.dev.js index 3b66addd..597258c9 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -100,6 +100,14 @@ module.exports = { }, { name: 'Test_Runner', alias: ['/plugins/Test_Runner.js', '/src/Test_Runner.js'] }, { name: 'config', alias: ['/plugins/config.js'] }, + { + name: 'IntentReader', + alias: ['/plugins/IntentReader.js', '/src/IntentReader.js'], + }, + { + name: 'RunTestHandler', + alias: ['/plugins/runTestHandler.js', '/src/pubsub/handlers/RunTestHandler.js'], + }, ], 'resolve' ), diff --git a/webpack.prod.js b/webpack.prod.js index d2937131..b9d0533f 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -83,6 +83,14 @@ module.exports = { }, { name: 'Test_Runner', alias: ['/plugins/Test_Runner.js', '/src/Test_Runner.js'] }, { name: 'config', alias: ['/plugins/config.js'] }, + { + name: 'IntentReader', + alias: ['/plugins/IntentReader.js', '/src/IntentReader.js'], + }, + { + name: 'RunTestHandler', + alias: ['/plugins/runTestHandler.js', '/src/pubsub/handlers/RunTestHandler.js'], + }, ], 'resolve' ),