diff --git a/ISSUES.md b/ISSUES.md index 3974c98..803edc6 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -18,6 +18,15 @@ * MessageStart seems to be starting even before the message has been received. * Designer also seems to be showing invalid editor for MessageStart event +# taskId populated in process-token for the user-task + * contains only one taskId. This works well if it is a normal user task. + * In case of multi-instance tasks, + * Series - works well as it populated one task id after the other. + * Parallel - it still contains only one task id even though there are many tasks created. + +# if user task some expression evaluation fails +previous task remains pending and flow execution stops + # Call-Activity Designer Input/Output-Parameters * Call-Activity node in designer does not have means to specify below data and hence this part is not tested fully. (Refer call-activity-main.bpmn: Line#23-26) ``` xml diff --git a/bin/app-list.json b/bin/app-list.json deleted file mode 100644 index 582344f..0000000 --- a/bin/app-list.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "path": "oe-cloud", - "enabled": true - }, - { - "path": "oe-master-job-executor", - "enabled": true - }, - { - "path": "oe-business-rule", - "enabled": true - }, - { - "path": "./", - "forceLoad": "index.js", - "enabled": true - }, - { - "path": "./", - "enabled": true, - "serverDir": "bin" - } -] diff --git a/bin/app.js b/bin/app.js deleted file mode 100644 index 8efcc77..0000000 --- a/bin/app.js +++ /dev/null @@ -1,13 +0,0 @@ -var oecloud = require('oe-cloud'); - -oecloud.observe('loaded', function loadedCb(ctx, next) { - return next(); -}); - -oecloud.boot(__dirname, function bootCb(err) { - if (err) { - throw err; - } - oecloud.start(); - oecloud.emit('oe-workflow-app-started'); -}); diff --git a/bin/boot/root.js b/bin/boot/root.js deleted file mode 100644 index a1caf61..0000000 --- a/bin/boot/root.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -module.exports = function addDefaultRoute(server) { - // Install a `/` route that returns server status - var router = new server.loopback.Router(); - router.get('/', server.loopback.status()); - server.use(router); -}; diff --git a/bin/component-config.json b/bin/component-config.json deleted file mode 100644 index 5436fc7..0000000 --- a/bin/component-config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "oe-workflow-modeler/component": { - "mountPath": "/wfmodeler", - "extensionsPath": "../../bin/pallet-entries.json" - } -} diff --git a/bin/config.json b/bin/config.json deleted file mode 100644 index a209278..0000000 --- a/bin/config.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "restApiRoot": "/api", - "host": "0.0.0.0", - "port": 3000, - "remoting": { - "context": false, - "rest": { - "normalizeHttpPath": false, - "xml": false - }, - "json": { - "strict": false, - "limit": "100kb" - }, - "urlencoded": { - "extended": true, - "limit": "100kb" - }, - "cors": false, - "handleErrors": false - }, - "client":{ - "templatePath": ["/client/templates"] - }, - "workflow":{ - "addonModule": "utils/addon-functions", - "recovery": { - "retryInterval": 600000, - "stalePeriod": 30000 - } - }, - "masterJobExecutor": { - "initDelay": 1000, - "checkMasterInterval":2000, - "heartbeatInterval":1000, - "masterJobTolerance": 3000 - }, - "subPath":"appsubpath", - "legacyExplorer": false -} diff --git a/bin/datasources.docker.js b/bin/datasources.docker.js deleted file mode 100644 index 55d87d4..0000000 --- a/bin/datasources.docker.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var mongoPort = process.env.MONGO_PORT ? parseInt(process.env.MONGO_PORT) : 27017; -var dbName = process.env.DB_NAME || 'oe-workflow-test'; -module.exports = { - 'memdb': { - 'name': 'memdb', - 'connector': 'memory' - }, - 'transient': { - 'name': 'transient', - 'connector': 'transient' - }, - 'db': { - 'host': mongoHost, - 'port': mongoPort, - 'url': 'mongodb://' + mongoHost + ':' + mongoPort + '/' + dbName, - 'database': dbName, - 'name': 'db', - 'connector': 'oe-connector-mongodb', - 'connectionTimeout': 500000 - } -}; - diff --git a/bin/datasources.json b/bin/datasources.json deleted file mode 100644 index 809eb7a..0000000 --- a/bin/datasources.json +++ /dev/null @@ -1,13 +0,0 @@ -{ -"db": { -"host": "localhost", -"port": 27017, -"url": "mongodb://localhost:27017/oe-workflow", -"database": "oe-workflow", -"password": "admin", -"name": "db", -"connector": "oe-connector-mongodb", -"user": "admin", -"connectionTimeout": 50000 -} -} \ No newline at end of file diff --git a/bin/datasources.mongo.js b/bin/datasources.mongo.js deleted file mode 100644 index 55d87d4..0000000 --- a/bin/datasources.mongo.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var mongoPort = process.env.MONGO_PORT ? parseInt(process.env.MONGO_PORT) : 27017; -var dbName = process.env.DB_NAME || 'oe-workflow-test'; -module.exports = { - 'memdb': { - 'name': 'memdb', - 'connector': 'memory' - }, - 'transient': { - 'name': 'transient', - 'connector': 'transient' - }, - 'db': { - 'host': mongoHost, - 'port': mongoPort, - 'url': 'mongodb://' + mongoHost + ':' + mongoPort + '/' + dbName, - 'database': dbName, - 'name': 'db', - 'connector': 'oe-connector-mongodb', - 'connectionTimeout': 500000 - } -}; - diff --git a/bin/datasources.oracle.js b/bin/datasources.oracle.js deleted file mode 100644 index a5db290..0000000 --- a/bin/datasources.oracle.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var oracleSID = process.env.ORACLE_SID || 'ORCLCDB'; -var oracleHost = process.env.ORACLE_HOST || 'localhost'; -var oraclePort = process.env.ORACLE_PORT ? parseInt(process.env.ORACLE_PORT) : 1521; -var oracleUserName = process.env.ORACLE_USERNAME || 'oeadmin'; -var oracleUserPassword = process.env.ORACLE_PASSWORD || 'oeadmin'; - -module.exports = { - 'nullsrc': { - 'name': 'nullsrc', - 'connector': 'memory' - }, - 'transient': { - 'name': 'transient', - 'connector': 'transient' - }, - 'db': { - 'name': 'db', - 'connector': 'oe-connector-oracle', - 'database': oracleSID, - 'host': oracleHost, - 'port': oraclePort, - 'password': oracleUserPassword, - 'user': oracleUserName - } -}; - - diff --git a/bin/datasources.postgres.js b/bin/datasources.postgres.js deleted file mode 100644 index f943543..0000000 --- a/bin/datasources.postgres.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var postgresHost = process.env.POSTGRES_HOST || 'localhost'; -var postgresPort = process.env.POSTGRES_PORT ? parseInt(process.env.POSTGRES_PORT) : 5432; -var dbName = process.env.DB_NAME || 'oe-workflow-test'; -module.exports = { - 'memdb': { - 'name': 'memdb', - 'connector': 'memory' - }, - 'transient': { - 'name': 'transient', - 'connector': 'transient' - }, - 'db': { - 'host': postgresHost, - 'port': postgresPort, - 'url': 'postgres://postgres:postgres@' + postgresHost + ':' + postgresPort + '/' + dbName, - 'database': dbName, - 'password': 'postgres', - 'name': 'db', - 'connector': 'oe-connector-postgresql', - 'user': 'postgres', - 'max': 50, - 'connectionTimeout': 50000 - } -}; diff --git a/common/activiti-models/activiti-manager.js b/common/activiti-models/activiti-manager.js index 3c33690..ec43010 100755 --- a/common/activiti-models/activiti-manager.js +++ b/common/activiti-models/activiti-manager.js @@ -32,7 +32,8 @@ module.exports = function ActivitiManager(ActivitiManager) { // ActivitiManager.disableRemoteMethod('deleteWithVersion', true); - ActivitiManager.enable = function enable(baseUrl, options, cb) { + ActivitiManager.enable = function enable(payload, options, cb) { + let baseUrl = payload.baseUrl; var app = ActivitiManager.app; helper._enableActiviti(baseUrl, app, options, cb); }; @@ -152,10 +153,20 @@ module.exports = function ActivitiManager(ActivitiManager) { ActivitiManager.remoteMethod('enable', { description: 'enable Activiti rest endpoints', - accepts: { - arg: 'baseUrl', - description: 'Activiti server URL to connect', - type: 'string' + accepts: [{ + arg: 'payload', + type: 'object', + http: { + source: 'body' + }, + description: 'Activiti server URL to connect' + }, { + arg: 'options', + type: 'object', + http: 'optionsFromRequest' + }], + http: { + verb: 'post' }, returns: { type: 'object', diff --git a/common/activiti-models/lib/helper.js b/common/activiti-models/lib/helper.js index 05fe59a..5557197 100755 --- a/common/activiti-models/lib/helper.js +++ b/common/activiti-models/lib/helper.js @@ -19,6 +19,13 @@ exports._enableActiviti = function _enableActiviti(baseUrl, app, ctx, done) { ctx = {}; } + if (!baseUrl) { + let err = new Error('Activiti endpoint must be specified'); + err.statusCode = err.status = 422; + err.code = 'INVALID_DATA'; + return done(err); + } + var modelNames = [ 'activiti-deployment', 'activiti-engine', diff --git a/common/mixins/maker-checker-mixin-v2.js b/common/mixins/maker-checker-mixin-v2.js index a82a607..56aafcd 100755 --- a/common/mixins/maker-checker-mixin-v2.js +++ b/common/mixins/maker-checker-mixin-v2.js @@ -689,6 +689,12 @@ function addOERemoteMethods(Model) { } delete context.isNewChangeRequest; + context.beforeWorkflow = true; + let observerName = 'before save'; + var wfConfig = Model.app.get('workflow') || {}; + if (wfConfig.disableMakerCheckerBeforeSave) { + observerName = 'dummy observer'; + } // var RootModel = Model; // var beforeSaveArray = Model._observers['before save'] || []; @@ -707,7 +713,8 @@ function addOERemoteMethods(Model) { // return next(err); // } // dpBeforeSave[0](context, function beforeSaveCb(err) { - Model.notifyObserversOf('before save', context, function beforeSaveCb(err) { + Model.notifyObserversOf(observerName, context, function beforeSaveCb(err) { + delete context.beforeWorkflow; if (err) return next(err); if (context.currentInstance) { @@ -732,7 +739,7 @@ function addOERemoteMethods(Model) { // log.error(options, err); return next(err); } - }, data, context); + }, data, context.options); }); }); } @@ -741,6 +748,11 @@ function addOERemoteMethods(Model) { // get hasOne, hasMany relation metadata var relations = []; var childData = {}; + var validateFunction = async.map; + var wfConfig = Model.app.get('workflow') || {}; + if (wfConfig.disableMakerCheckerParallelValidations) { + validateFunction = async.mapSeries; + } for (let r in Model.relations) { if (Object.prototype.hasOwnProperty.call(Model.relations, r)) { let relation = Model.relations[r]; @@ -774,7 +786,7 @@ function addOERemoteMethods(Model) { return next(err); } - async.map(relations, + validateFunction(relations, function validateEach(relation, cb) { let Model = relation.Model; let data = relation.data; @@ -810,7 +822,7 @@ function addOERemoteMethods(Model) { return next(err); } - async.map(relations, + validateFunction(relations, function validateEach(relation, cb) { let Model = relation.Model; let data = relation.data; diff --git a/common/mixins/workflow-mixin.js b/common/mixins/workflow-mixin.js index bac7923..b97ecff 100644 --- a/common/mixins/workflow-mixin.js +++ b/common/mixins/workflow-mixin.js @@ -185,13 +185,13 @@ function addRemoteHooks(Model) { version: 'v0' }; - if (operationList.create.indexOf(method) !== -1) { + if (operationList.create.indexOf(method) >= 0) { operation = 'create'; filter.operation = { inq: ['save', 'create'] }; - } else if (operationList.update.indexOf(method) !== -1) { + } else if (operationList.update.indexOf(method) >= 0) { filter.operation = { inq: ['save', 'update'] }; operation = 'update'; - } else if (operationList.delete.indexOf(method) !== -1) { + } else if (operationList.delete.indexOf(method) >= 0) { operation = 'delete'; filter.operation = operation; } else { diff --git a/common/models/process-definition.js b/common/models/process-definition.js index 71c7189..d2cb96a 100755 --- a/common/models/process-definition.js +++ b/common/models/process-definition.js @@ -44,7 +44,9 @@ module.exports = function ProcessDefinition(ProcessDefinition) { }); } /* Check for duplicate names */ - let nameCount = flowObjects.map(fo => fo.name).reduce((s, foName) => { + /* whlie checking for duplicate names, ignore link events */ + let nonLinkflowObjects = flowObjects.filter(fo => !fo.isLinkEvent); + let nameCount = nonLinkflowObjects.map(fo => fo.name).reduce((s, foName) => { s[foName] = s[foName] || 0; s[foName] = s[foName] + 1; return s; @@ -313,8 +315,10 @@ module.exports = function ProcessDefinition(ProcessDefinition) { * @return {Object} IndexMap */ function _buildNameMap(objects) { + /* whlie checking for duplicate names, ignore link events */ + let nonLinkflowObjects = objects.filter(fo => !fo.isLinkEvent); var map = {}; - objects.forEach(function iterateObjects(object) { + nonLinkflowObjects.forEach(function iterateObjects(object) { var name = object.name; if (map[name]) { log.error(log.defaultContext(), "Process element name '" + name + "' must be unique."); diff --git a/common/models/process-instance.js b/common/models/process-instance.js index cc111fc..3310803 100755 --- a/common/models/process-instance.js +++ b/common/models/process-instance.js @@ -256,9 +256,26 @@ module.exports = function ProcessInstance(ProcessInstance) { if (outputVariables && task.message && typeof task.message === 'object' && typeof outputVariables === 'object') { Object.assign(task.message, outputVariables); } - // to disable boundary timer event if task is completed beforehand - self._clearBoundaryTimerEvents(delta, options, processDefinition.getFlowObjectByName(token.name)); - self._endFlowObject(options, token, processDefinition, delta, message, next); + let taskObj = processDefinition.getFlowObjectByName(token.name); + let postCompleteFunction = function postCompleteFunction(options, task, cb) { + /* default do-nothing */ + return cb(options, task); + }; + let workflowAddons = ProcessInstance.app.workflowAddons || {}; + if (taskObj.postCompletionHook) { + if (workflowAddons[taskObj.postCompletionHook]) { + postCompleteFunction = workflowAddons[taskObj.postCompletionHook]; + } else { + log.error('postComplete function ' + taskObj.postCompletionHook + ' not defined'); + } + } else if (workflowAddons.defaultTaskPostCompletionHook) { + postCompleteFunction = workflowAddons.defaultTaskPostCompletionHook; + } + postCompleteFunction.call(self, options, task, function postCompleteCallBack(options, task) { + // to disable boundary timer event if task is completed beforehand + self._clearBoundaryTimerEvents(delta, options, processDefinition.getFlowObjectByName(token.name)); + self._endFlowObject(options, token, processDefinition, delta, message, next); + }); }; /** @@ -328,7 +345,7 @@ module.exports = function ProcessInstance(ProcessInstance) { token.isUserTask = true; } /* for now ConditionalEvents implementation is done only for Conditional Intermediate Catch Events */ - if (obj.isConditionalEvent && obj.isIntermediateCatchEvent) { + if (obj.isConditionalEvent && (obj.isIntermediateCatchEvent || obj.isBoundaryEvent)) { token.isConditionalEvent = true; if (obj.pvName) token.pvName = obj.pvName; } @@ -649,7 +666,7 @@ module.exports = function ProcessInstance(ProcessInstance) { if (token.elementVariable && token.collection) { token.inVariables[token.elementVariable] = token.collection[i]; } - token.inVariables._iteration = i; + token.inVariables._iteration = i + 1; var _token = _.cloneDeep(token); ProcessInstance.emit(TOKEN_ARRIVED_EVENT, options, ProcessInstance, instance, _token); diff --git a/common/models/task.js b/common/models/task.js index e6cba33..b1b014e 100755 --- a/common/models/task.js +++ b/common/models/task.js @@ -15,6 +15,7 @@ var log = logger('Task'); var taskEventHandler = require('../../lib/workflow-eventHandlers/taskeventhandler.js'); var TASK_INTERRUPT_EVENT = 'TASK_INTERRUPT_EVENT'; +var dateUtils = require('../../lib/utils/oe-date-utils.js'); module.exports = function Task(Task) { Task.disableRemoteMethodByName('create', true); @@ -280,7 +281,17 @@ module.exports = function Task(Task) { var instObj = inst.toObject(); var operation = instObj.operation; /* For second-maker currentInstance should have partially changed data from change-request */ - currentInstance = new Model(instObj.data); + // currentInstance = new Model(instObj.data); + + /* For update, the currentInstance should not be null */ + if (operation === 'update' && !currentInstance) { + let msg = 'Record with id:' + modelId + ' not found.'; + let error = new Error(msg); + error.statusCode = error.status = 404; + error.code = 'MODEL_NOT_FOUND'; + return next(error); + } + var instx = JSON.parse(JSON.stringify(instObj.data)); for (let key in updates) { if (Object.prototype.hasOwnProperty.call(updates, key)) { @@ -334,6 +345,8 @@ module.exports = function Task(Task) { if (!data.__action__) { let err = new Error('__action__ not provided. Checker enabled task requires this field.'); + err.statusCode = err.status = 422; + err.code = 'ACTION_REQUIRED'; log.error(options, err); return next(err); } @@ -346,6 +359,8 @@ module.exports = function Task(Task) { let isValid = (validActArr.indexOf(data.__action__) > -1); if (!isValid) { let err = new Error('Provided action is not valid. Possible valid actions : ' + JSON.stringify(validActArr)); + err.statusCode = err.status = 422; + err.code = 'INVALID_ACTION'; log.error(options, err); return next(err); } @@ -394,6 +409,8 @@ module.exports = function Task(Task) { if (!data.__action__) { let err = new Error('__action__ not provided. Checker enabled task requires this field.'); + err.statusCode = err.status = 422; + err.code = 'ACTION_REQUIRED'; // log.error(options, err); return next(err); } @@ -406,6 +423,8 @@ module.exports = function Task(Task) { let isValid = (validActArr.indexOf(data.__action__) > -1); if (!isValid) { let err = new Error('Provided action is not valid. Possible valid actions : ' + JSON.stringify(validActArr)); + err.statusCode = err.status = 422; + err.code = 'INVALID_ACTION'; // log.error(options, err); return next(err); } @@ -508,7 +527,10 @@ module.exports = function Task(Task) { } if (self.status !== 'pending') { - return next(new Error('Task already completed')); + let error = new Error('Task already completed'); + error.code = 'TASK_ALREADY_COMPLETED'; + error.status = error.statusCode = 409; + return next(error); } self.processInstance({}, options, function fetchPI(err, processInstance) { /* istanbul ignore if*/ @@ -643,6 +665,57 @@ module.exports = function Task(Task) { }); }; + /** + * REST endpoint for updating followUpDate + * @param {objet} data followUpDate + * @param {Object} options Options + * @param {Function} next Callback + * @returns {void} + */ + + // FollowUpDate should be in DD-MM-YYYY format or any kind of date expressions like tod, tom, 5m, 30d. + Task.prototype.updateFollowUpDate = function followUpDate(data, options, next) { + let newFollowUpDate; + if (this.status !== 'pending') { + let error = new Error('Task already completed'); + error.code = 'TASK_ALREADY_COMPLETED'; + error.status = error.statusCode = 409; + return next(error); + } + if (data && data.followUpDate) { + newFollowUpDate = dateUtils.parseShorthand(data.followUpDate, 'DD-MM-YYYY'); + // passing invalid date will make newFollowUpDate as undefined + if (!newFollowUpDate) { + let error = new Error('Invalid date format'); + error.code = 'INVALID_DATA'; + error.status = error.statusCode = 422; + return next(error); + } + } else if (data && data.followUpDate === null) { + // followUpDate can be set to null + newFollowUpDate = null; + } else { + let error = new Error('follow up date is required'); + error.code = 'INVALID_DATA'; + error.status = error.statusCode = 400; + return next(error); + } + + var updates = { + _version: this._version, + followUpDate: newFollowUpDate + }; + + this.updateAttributes(updates, options, function updateAttributesCbFn(err, data) { + if (err) { + next(err); + } else { + next(null, data); + } + }); + }; + + Task.remoteMethod('complete', { accessType: 'WRITE', accepts: [{ @@ -742,4 +815,31 @@ module.exports = function Task(Task) { root: true } }); + + Task.remoteMethod('updateFollowUpDate', { + accessType: 'WRITE', + accepts: [{ + arg: 'data', + type: 'object', + required: true, + description: 'Task instance', + http: { + source: 'body' + } + }, { + arg: 'options', + type: 'object', + http: 'optionsFromRequest' + }], + description: 'Sends a request to update follow up dates', + http: { + verb: 'put', + path: '/updateFollowUpDate/' + }, + isStatic: false, + returns: { + type: 'object', + root: true + } + }); }; diff --git a/common/models/workflow-manager.js b/common/models/workflow-manager.js index 4c9fcf9..69b4473 100755 --- a/common/models/workflow-manager.js +++ b/common/models/workflow-manager.js @@ -56,12 +56,14 @@ module.exports = function WorkflowManager(WorkflowManager) { if (!data.operation) { err = new Error('operation parameter is required'); + err.statusCode = err.status = 422; err.code = 'INVALID_MAPPING_DATA'; // log.error(options, err); return cb(err); } else if (!(data.operation === 'create' || data.operation === 'update' || data.operation === 'delete' || data.operation === 'save' || data.operation === 'custom')) { err = new Error('operation is not valid'); + err.statusCode = err.status = 422; err.code = 'INVALID_MAPPING_DATA'; // log.error(options, err); return cb(err); @@ -82,12 +84,14 @@ module.exports = function WorkflowManager(WorkflowManager) { if (!data.modelName) { err = new Error('modelName parameter is required'); + err.statusCode = err.status = 422; err.code = 'INVALID_MAPPING_DATA'; // log.error(options, err); return cb(err); // } else if (typeof app.models[data.modelName] === 'undefined') { } else if (typeof loopback.findModel(data.modelName, options) === 'undefined') { err = new Error('modelName is not valid'); + err.statusCode = err.status = 422; err.code = 'INVALID_MAPPING_DATA'; // log.error(options, err); return cb(err); @@ -122,18 +126,19 @@ module.exports = function WorkflowManager(WorkflowManager) { } function validateWorkflowDeployment(name, done) { - var filter = { 'and': [{ 'name': name }, { 'latest': true }] }; - app.models.WorkflowDefinition.find({ - 'where': filter - }, options, function fetchWD(err, wfDefns) { + var filter = {where: { and: [{ name: name }, { latest: true }] }}; + app.models.WorkflowDefinition.find(filter, options, function fetchWD(err, wfDefns) { if (err) { return done(err); } else if (wfDefns.length === 0) { err = new Error('workflow definition not found'); + err.statusCode = err.status = 422; err.code = 'INVALID_MAPPING_DATA'; return done(err); } else if (wfDefns.length > 1) { - return done(new Error('multiple workflow definitions found')); + err = new Error('multiple workflow definitions found'); + err.statusCode = err.status = 501; + return done(err); } done(null); }); diff --git a/lib/parsing/events.js b/lib/parsing/events.js index a72df43..b40259c 100644 --- a/lib/parsing/events.js +++ b/lib/parsing/events.js @@ -96,6 +96,10 @@ function isTerminateEventName(localName) { return (localName.endsWith('terminateEventDefinition')); } +function isLinkEventName(localName) { + return (localName.endsWith('linkEventDefinition')); +} + exports.createBPMNEvent = function createBPMNEvent(defObject) { // Every Flow object(Event) is expected to have bpmnId, name and type var bpmnId = defObject.attributes_.id.value; @@ -111,7 +115,6 @@ exports.createBPMNEvent = function createBPMNEvent(defObject) { var finalEvent; var cancelActivity = ''; var attachedToRef; - // We are differentiating between Start, End, Intermediate and Boundary Events // If any of the events has a definition is defined, the event object, finalEvent will be populated with // defintion in the succeeding For-In loop below. @@ -188,6 +191,11 @@ exports.createBPMNEvent = function createBPMNEvent(defObject) { if (defObject[eventDefinition].attributes_ && defObject[eventDefinition].attributes_['camunda:variableName']) { finalEvent.pvName = defObject[eventDefinition].attributes_['camunda:variableName'].value; } + } else if (isLinkEventName(eventDefinitionType)) { + finalEvent.isLinkEvent = true; + if (defObject[eventDefinition].attributes_ && defObject[eventDefinition].attributes_.name.value) { + finalEvent.linkName = defObject[eventDefinition].attributes_.name.value; + } } } } diff --git a/lib/parsing/parse-utils/in-out-mappings.js b/lib/parsing/parse-utils/in-out-mappings.js index 9cc6dfa..7862b11 100644 --- a/lib/parsing/parse-utils/in-out-mappings.js +++ b/lib/parsing/parse-utils/in-out-mappings.js @@ -43,6 +43,7 @@ function createInputOutputMappings(inputOutputParameter) { if (source === 'variables') { target = 'all'; } else if (source && source !== '') { + source = source.replace(/\W/g, ''); if (input.attributes_ && input.attributes_.target) { target = input.attributes_.target.value; } else { diff --git a/lib/parsing/parse-utils/input-output-parameters.js b/lib/parsing/parse-utils/input-output-parameters.js index a0b7168..04c331c 100644 --- a/lib/parsing/parse-utils/input-output-parameters.js +++ b/lib/parsing/parse-utils/input-output-parameters.js @@ -32,13 +32,21 @@ function createInputOuputParameters(inputOutputParameter) { var input; var listVals; var mapVals; + var value; if (typeof inputOutputParameter !== 'undefined' && inputOutputParameter.constructor.name === 'Array') { for (input of inputOutputParameter) { if (input.attributes_ && input.attributes_.name) { inputId = input.attributes_.name.value; } if (input.hasOwnProperty('camunda:script')) { - inputVal = input['camunda:script'].text; + let scriptText = input['camunda:script'].text; + /* purely for supporting old functionality i.e. expressions which are enclosed inside '${}' + now ideally expressions no need to be enclosed in '${}' */ + if (scriptText.startsWith('${') && scriptText.endsWith('}')) { + inputVal = scriptText; + } else { + inputVal = '@script__' + scriptText; + } } if (input.hasOwnProperty('camunda:list')) { listVals = input['camunda:list']['camunda:value']; @@ -52,13 +60,25 @@ function createInputOuputParameters(inputOutputParameter) { } if (input.hasOwnProperty('camunda:map')) { mapVals = input['camunda:map']['camunda:entry']; + if (mapVals.constructor && mapVals.constructor.name !== 'Array') { + mapVals = [mapVals]; + } inputVal = {}; objKey = ''; for (var obj of mapVals) { if (obj.attributes_ && obj.attributes_.key) { objKey = obj.attributes_.key.value; } - inputVal[objKey] = obj.text; + /* obj.text will be a string, so convert that string to other datatypes based on value */ + value = obj.text; + if (!isNaN(value)) { + value = Number(value); + } else if (value === 'true') { + value = true; + } else if (value === 'false') { + value = false; + } + inputVal[objKey] = value; } } if (input.hasOwnProperty('text')) { @@ -72,7 +92,14 @@ function createInputOuputParameters(inputOutputParameter) { inputId = input.attributes_.name.value; } if (input.hasOwnProperty('camunda:script')) { - inputVal = input['camunda:script'].text; + let scriptText = input['camunda:script'].text; + /* purely for supporting old functionality i.e. expressions which are enclosed inside '${}' + now ideally expressions no need to be enclosed in '${}' */ + if (scriptText.startsWith('${') && scriptText.endsWith('}')) { + inputVal = scriptText; + } else { + inputVal = '@script__' + scriptText; + } } if (input.hasOwnProperty('camunda:list')) { listVals = input['camunda:list']['camunda:value']; @@ -86,13 +113,25 @@ function createInputOuputParameters(inputOutputParameter) { } if (input.hasOwnProperty('camunda:map')) { mapVals = input['camunda:map']['camunda:entry']; + if (mapVals.constructor && mapVals.constructor.name !== 'Array') { + mapVals = [mapVals]; + } inputVal = {}; objKey = ''; for (let obj of mapVals) { if (obj.attributes_ && obj.attributes_.key) { objKey = obj.attributes_.key.value; } - inputVal[objKey] = obj.text; + /* obj.text will be a string, so convert that string to other datatypes based on value */ + value = obj.text; + if (!isNaN(value)) { + value = Number(value); + } else if (value === 'true') { + value = true; + } else if (value === 'false') { + value = false; + } + inputVal[objKey] = value; } } if (input.hasOwnProperty('text')) { diff --git a/lib/parsing/tasks.js b/lib/parsing/tasks.js index 5c08443..27996e4 100644 --- a/lib/parsing/tasks.js +++ b/lib/parsing/tasks.js @@ -140,6 +140,9 @@ BPMNTask.prototype.addUserTaskAttributes = function addUserTaskAttributes(defObj if (defObject.attributes_[propertyType + 'completionHook']) { currentProcessElement.completionHook = defObject.attributes_[propertyType + 'completionHook'].value; } + if (defObject.attributes_['oecloud:postCompletionHook']) { + currentProcessElement.postCompletionHook = defObject.attributes_['oecloud:postCompletionHook'].value; + } if (defObject['bpmn2:extensionElements'] && defObject['bpmn2:extensionElements']['camunda:inputOutput']) { currentProcessElement.inputOutputParameters = extractInputOutputParameters(defObject['bpmn2:extensionElements']['camunda:inputOutput']); } @@ -220,6 +223,9 @@ BPMNTask.prototype.addBusinessRuleTaskAttributes = function addBusinessRuleTaskA if (defObject['bpmn2:extensionElements'] && defObject['bpmn2:extensionElements']['camunda:inputOutput']) { this.inputOutputParameters = extractInputOutputParameters(defObject['bpmn2:extensionElements']['camunda:inputOutput']); } + if (defObject.attributes_['oecloud:mode']) { + this.mode = defObject.attributes_['oecloud:mode'].value; + } }; function addUserTaskFormVariables(defObject, taskObject) { diff --git a/lib/utils/process-state-delta.js b/lib/utils/process-state-delta.js index 8941889..df97f90 100644 --- a/lib/utils/process-state-delta.js +++ b/lib/utils/process-state-delta.js @@ -246,6 +246,11 @@ Delta.prototype.apply = function apply(zInstance, processDefinitionInstance, opt }); } + // updating taskId on token + if (this.tokensToupdate) { + tokens[this.tokensToupdate.tokenId].taskId = this.tokensToupdate.taskId; + } + if (setTocomplete) { var res = this.applyTokens(tokens, synchronizeFlow); if (res === null) { @@ -680,3 +685,10 @@ Delta.prototype.updateProcessTimerEvents = function updateProcessTimerEvents(pro } } }; + +Delta.prototype.updateTaskIdOnToken = function updateTaskIdOnToken(tokenId, taskId) { + this.tokensToupdate = { + tokenId: tokenId, + taskId: taskId + }; +}; diff --git a/lib/utils/tokenemission.js b/lib/utils/tokenemission.js index 232182c..d786c0c 100644 --- a/lib/utils/tokenemission.js +++ b/lib/utils/tokenemission.js @@ -26,7 +26,14 @@ exports.getNextFlowObjects = function getNextFlowObjects(currentFlowObject, mess var expectedObjects = getIncomingSequenceFlows(flowObject, processDefinitionInstance); flowObject.expectedInFlows = expectedObjects.map(function mapFlowObjects(flow) { return flow.bpmnId; }); } - nextFlowObjects.push(flowObject); + if (flowObject.isLinkEvent) { + let flowObjectsAttachedToLink = getFlowObjectsAttachedToLink(flowObject.linkName, processDefinitionInstance); + flowObjectsAttachedToLink && flowObjectsAttachedToLink.forEach(flowObject => { + nextFlowObjects.push(flowObject); + }); + } else { + nextFlowObjects.push(flowObject); + } }; var i; @@ -130,3 +137,19 @@ var getOutgoingSequenceFlows = function getOutgoingSequenceFlows(flowObject, pro var getIncomingSequenceFlows = function getIncomingSequenceFlows(flowObject, processDefinition) { return processDefinition._getFlows('sequenceFlowBySourceTarget', flowObject); }; + +var getFlowObjectsAttachedToLink = function getFlowObjectsAttachedToLink(linkName, processDefinitionInstance) { + let flowObjects = []; + /* According to BPMN 2.0 specs 'There MUST NOT be multiple Target Links for a single Source Link.' */ + let respectiveLinkFlowObject = processDefinitionInstance.processDefinition.flowObjects.find(flowObject => { + return flowObject.isIntermediateCatchEvent && flowObject.linkName === linkName; + }); + let sequenceFlows = processDefinitionInstance.processDefinition.sequenceFlows.filter(sequenceFlow => { + return sequenceFlow.sourceRef === respectiveLinkFlowObject.bpmnId; + }); + sequenceFlows && sequenceFlows.forEach(sequenceFlow => { + flowObjects.push(processDefinitionInstance.getProcessElement(sequenceFlow.targetRef)); + }); + + return flowObjects; +}; diff --git a/lib/workflow-eventHandlers/tokeneventhandler.js b/lib/workflow-eventHandlers/tokeneventhandler.js index 46e99fb..78e6a7f 100644 --- a/lib/workflow-eventHandlers/tokeneventhandler.js +++ b/lib/workflow-eventHandlers/tokeneventhandler.js @@ -135,7 +135,7 @@ exports._tokenArrivedEventHandler = function _tokenArrivedEventHandler(options, } } entityList = sandbox.evaluate$Expression(options, entityList, token.message, currentProcess, token.inVariables); - if (entityList === '' || entityList === 'undefined') { + if (!entityList || entityList === 'undefined') { return []; } return entityList.split(',').map(v => { @@ -317,22 +317,15 @@ exports._tokenArrivedEventHandler = function _tokenArrivedEventHandler(options, if (err) { return log.error(options, err); } - /** In order to update the task-id on token, instead of doing commit on currentProcess, - * select the instance and do the commit to prevent failed updates in DB during parallel updates - */ - ProcessInstance.findById(currentProcess.id, options, function fetchInstance(err, instance) { + // updating taskId on token + let newDelta = new StateDelta(); + newDelta.updateTaskIdOnToken(token.id, task.id.toString()); + currentProcess.commit(options, newDelta, function commitCb(err, inst) { + /* istanbul ignore if*/ if (err) { - log.error(options, 'Error Selecting : ' + err.message); - instance = currentProcess; + return log.error(options, err); } - instance._processTokens[token.id].taskId = task.id; - instance.commit(options, new StateDelta(), function commitCb(err, inst) { - /* istanbul ignore if*/ - if (err) { - return log.error(options, err); - } - ProcessInstance.app.models.Task.emit(inst.processDefinitionName + '-' + task.name, task, inst); - }); + ProcessInstance.app.models.Task.emit(inst.processDefinitionName + '-' + task.name, task, inst); }); }); }); @@ -366,7 +359,7 @@ exports._tokenArrivedEventHandler = function _tokenArrivedEventHandler(options, if (currentFlowObject.inOutMappings && currentFlowObject.inOutMappings.inputMappings) { var inputMappings = currentFlowObject.inOutMappings.inputMappings; for (var source in inputMappings) { - if (Object.prototype.hasOwnProperty.call(inputMappings, source) && source === 'variables' && inputMappings[source] === 'all') { + if (Object.prototype.hasOwnProperty.call(inputMappings, source) && ((source === 'variables' && inputMappings[source] === 'all') || source === 'all')) { Object.assign(subProcessesIns.processVariables, currentProcess._processVariables); } else if (token.inVariables && source in token.inVariables) { // use multi-instance variables first diff --git a/lib/workflow-nodes/businessruletask-node.js b/lib/workflow-nodes/businessruletask-node.js index 72aa17d..ad2b565 100644 --- a/lib/workflow-nodes/businessruletask-node.js +++ b/lib/workflow-nodes/businessruletask-node.js @@ -17,8 +17,8 @@ var sandbox = require('./sandbox.js'); var exports = module.exports = {}; -exports.businessRuleTaskHandler = function businessRuleTaskHandler(ruleName, payload, message, process, options, done) { - var model = loopback.getModel('DecisionTable', options); +exports.businessRuleTaskHandler = function businessRuleTaskHandler(ruleName, mode, payload, message, process, options, done) { + var model = loopback.getModel(mode, options); /* istanbul ignore if*/ if (!model) { log.error(options, 'Rule engine is not enabled'); @@ -32,10 +32,18 @@ exports.businessRuleTaskHandler = function businessRuleTaskHandler(ruleName, pay let evalPayload = evaluatePayload(payload, message, process); ruleName = sandbox.evaluate$Expression(options, ruleName, message, process); - model.exec(ruleName, evalPayload, options, function execBR(err, res) { + if (mode === 'DecisionTable') { + model.exec(ruleName, evalPayload, options, executeBusinessRuleCB); + } else if (mode === 'DecisionService') { + model.invoke(ruleName, evalPayload, options, executeBusinessRuleCB); + } else if (mode === 'DecisionTree') { + model.exec(ruleName, evalPayload, options, executeBusinessRuleCB); + } + + function executeBusinessRuleCB(err, res) { let message = { documentName: ruleName, - data: evalPayload + input: evalPayload }; if (err) { log.error(options, err.message); @@ -45,10 +53,10 @@ exports.businessRuleTaskHandler = function businessRuleTaskHandler(ruleName, pay }; } if (res) { - message.body = res; + message.output = res; } return done(null, message); - }); + } }; var evaluatePayload = function evalPayload(inputData, message, process) { @@ -67,16 +75,29 @@ var evaluatePayload = function evalPayload(inputData, message, process) { } var payload = {}; + var value; for (prop in inputData) { if (Object.prototype.hasOwnProperty.call(inputData, prop)) { var propVal = inputData[prop]; if (!(inputData[prop].constructor && (inputData[prop].constructor.name === 'Array' || inputData[prop].constructor.name === 'Object'))) { var propExp = inputData[prop].trim(); - var value = sandbox.evaluate$Expression({}, propExp, message, process); - if (propExp.startsWith('${') && propExp.endsWith('}')) { - var newValue = parseFloat(value); - if (!isNaN(newValue)) { - value = newValue; + if (propExp.startsWith('@script__')) { + value = sandbox.evaluateExpression({}, propExp.replace('@script__', ''), message, process); + } else { + value = sandbox.evaluate$Expression({}, propExp, message, process); + /* to support old functionality i.e. numbers or numerical expressions wihich are enclosed inside '${}'. + now ideally numbers or numerical expressions no need to be enclosed in '${}' */ + if (propExp.startsWith('${') && propExp.endsWith('}')) { + var newValue = parseFloat(value); + if (!isNaN(newValue)) { + value = newValue; + } + } else if (!isNaN(value)) { + value = Number(value); + } else if (value === 'true') { + value = true; + } else if (value === 'false') { + value = false; } } payload[prop] = value; diff --git a/lib/workflow-nodes/evaluate-flow-object.js b/lib/workflow-nodes/evaluate-flow-object.js index 7c39f8b..650b39a 100644 --- a/lib/workflow-nodes/evaluate-flow-object.js +++ b/lib/workflow-nodes/evaluate-flow-object.js @@ -49,7 +49,7 @@ module.exports.evaluate = function EvaluateFO(options, flowObject, incomingMsg, } serviceNode.run(options, flowObject, serviceVariables, process, token, done); } else if (flowObject.businessRuleTask) { - businessRuleTaskHandler(flowObject.ruleName, flowObject.inputOutputParameters.inputParameters, incomingMsg, process, options, done); + businessRuleTaskHandler(flowObject.ruleName, flowObject.mode, flowObject.inputOutputParameters.inputParameters, incomingMsg, process, options, done); } else if (flowObject.type === 'sendTask') { process.processDefinition({}, options, function fetchPD(err, processDefinitionInstance) { /* istanbul ignore if*/ diff --git a/lib/workflow-nodes/sandbox.js b/lib/workflow-nodes/sandbox.js index 87a8578..5e98b37 100644 --- a/lib/workflow-nodes/sandbox.js +++ b/lib/workflow-nodes/sandbox.js @@ -9,11 +9,10 @@ * @author Kangan Verma(kangan06), Mandeep Gill(mandeep6ill), Prem Sai(premsai-ch), Vivek Mittal(vivekmittal07) */ -var vm = require('vm'); -var _ = require('lodash'); - -var logger = require('oe-logger'); -var log = logger('Sandbox'); +const vm = require('vm'); +const _ = require('lodash'); +const nodeProcess = require('process'); +const log = require('oe-logger')('Sandbox'); let depMessage = '[TO BE DEPRECATED SOON]: Please update _getPV() with pv(), _setPV() with setPV() and _msg. with msg. in Expressions and Scripts'; @@ -66,6 +65,9 @@ module.exports.evaluateScript = function evaluateScript(options, script, incomin sendMsg: function sendMsg(msg) { message = msg; }, + getEnv: function getEnv(envVar) { + return nodeProcess.env[envVar] || ''; + }, _log: log.log, _msg: incomingMsg, _setPV: function _setPV(name, value) { @@ -74,7 +76,7 @@ module.exports.evaluateScript = function evaluateScript(options, script, incomin }, _getPV: function _getPV(name) { log.warn(depMessage); - return this.pv(name); + return context.pv(name); }, _sendMsg: function _sendMsg(msg) { log.warn(depMessage); @@ -165,6 +167,9 @@ module.exports.evaluateExpressions = function evalExpressions(options, expressio } return process._processVariables[name]; }, + getEnv: function getEnv(envVar) { + return nodeProcess.env[envVar] || ''; + }, _msg: msg, _getPV: function _getPV(name) { log.warn(depMessage); @@ -213,6 +218,13 @@ module.exports.evaluateDirect = function evaluateDirect(options, expression, mes sandbox[prop] = process._processVariables[prop]; }); + sandbox.getEnv = function getEnv(envVar) { + return nodeProcess.env[envVar] || ''; + }; + sandbox.pv = function pv(key) { + return process._processVariables[key]; + }; + var evalExpression = '_output = ' + expression; // eslint-disable-next-line @@ -235,6 +247,9 @@ module.exports.evaluate$Expression = function eval$Expression(options, expressio msg: msg, inVariables: inVariables, pv: process._processVariables, + getEnv: function getEnv(envVar) { + return nodeProcess.env[envVar] || ''; + }, _output: null }; diff --git a/lib/workflow-nodes/service-node.js b/lib/workflow-nodes/service-node.js index 483c9e9..3e7a98b 100644 --- a/lib/workflow-nodes/service-node.js +++ b/lib/workflow-nodes/service-node.js @@ -223,7 +223,7 @@ function evaluateRestConnector(options, flowObject, message, process, token, don function makeRESTCalls(urlOptions, retry, callback) { request(urlOptions, function makeRequest(err, response, body) { if (err) { - callback(err); + return callback(err); } var message = { urlOptions: urlOptions diff --git a/package.json b/package.json index 350737e..224fc02 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "oe-workflow", "description": "oe-workflow engine written using oecloud.io framework", - "version": "2.2.0", + "version": "2.3.0", "engines": { "node": ">=6" }, @@ -22,12 +22,11 @@ }, "dependencies": { "async": "2.6.1", - "iso8601-duration": "^1.2.0", - "loopback-connector-rest": "^3.6.0", + "iso8601-duration": "1.2.0", + "loopback-connector-rest": "3.7.0", "oe-cloud": "^2.0.0", "oe-common-mixins": "^2.0.0", "oe-master-job-executor": "^2.0.0", - "oe-workflow-modeler": "^2.2.0", "uuid": "3.3.2", "xml2js": "0.4.17" }, diff --git a/server/boot/01-load-addon.js b/server/boot/01-load-addon.js index a15ba18..ec211fb 100644 --- a/server/boot/01-load-addon.js +++ b/server/boot/01-load-addon.js @@ -16,7 +16,7 @@ module.exports = function recoverWorkflows(app) { let workflowAddons = {}; var wfConfig = app.get('workflow') || {}; if (wfConfig.addonModule) { - let modulePath = path.resolve(process.cwd(), wfConfig.addonModule); + let modulePath = path.resolve(wfConfig.addonModule); log.debug('Trying to load addon module at ' + modulePath); workflowAddons = require(modulePath); /* No try-catch. let it throw error during startup if specified addon-module is not found */ diff --git a/test/bpmn-files/DecisionTableData.json b/test/bpmn-files/DecisionTableData.json new file mode 100644 index 0000000..769fe68 --- /dev/null +++ b/test/bpmn-files/DecisionTableData.json @@ -0,0 +1,260 @@ +[ + { + "name": "eligibility_USA", + "decisionRules": "{\"inputExpressionList\":[\"amount\",\"type\",\"experience\"],\"outputs\":[\"eligibility\",\"preApproved\"],\"hitPolicy\":\"F\",\"ruleList\":[[\"<= 1000\",\"\\\"PERSONAL_LOAN\\\"\",\"> 6\",\"amount\",\"TRUE\"],[\"<=1000\",\"\\\"PERSONAL_LOAN\\\"\",\"-\",\"if amount < (monthlyIncome*6) then amount else (monthlyIncome*6)\",\"FALSE\"],[\"> 1000\",\"\\\"PERSONAL_LOAN\\\"\",\"-\",\"if amount < (monthlyIncome*6) then amount else (monthlyIncome*6)\",\"FALSE\"],[\"-\",\"\\\"HOME_LOAN\\\"\",\"-\",\"if amount < (monthlyIncome*72) then amount else (monthlyIncome*72)\",\"FALSE\"],[\"-\",\"\\\"CAR_LOAN\\\"\",\"-\",\"if amount < (monthlyIncome*12) then amount else (monthlyIncome*12)\",\"FALSE\"]],\"inputValues\":[\"<= 1000,<=1000,> 1000,-\",\"\\\"PERSONAL_LOAN\\\",\\\"HOME_LOAN\\\",\\\"CAR_LOAN\\\"\",\"> 6,-\"],\"outputValues\":[\"amount,if amount < (monthlyIncome*6) then amount else (monthlyIncome*6),if amount < (monthlyIncome*72) then amount else (monthlyIncome*72),if amount < (monthlyIncome*12) then amount else (monthlyIncome*12)\",\"TRUE,FALSE\"],\"context\":null}", + "dtDesignerData": { + "name": "", + "inputExpressionList": [ + { + "label": "amount", + "expr": "amount", + "type": "string", + "values": "<= 1000,<=1000,> 1000,-" + }, + { + "label": "type", + "expr": "type", + "type": "string", + "values": "\"PERSONAL_LOAN\",\"HOME_LOAN\",\"CAR_LOAN\"" + }, + { + "label": "experience", + "expr": "experience", + "type": "string", + "values": "> 6,-" + } + ], + "outputs": [ + { + "label": "eligibility", + "expr": "eligibility", + "type": "string", + "values": "amount,if amount < (monthlyIncome*6) then amount else (monthlyIncome*6),if amount < (monthlyIncome*72) then amount else (monthlyIncome*72),if amount < (monthlyIncome*12) then amount else (monthlyIncome*12)" + }, + { + "label": "preApproved", + "expr": "preApproved", + "type": "string", + "values": "TRUE,FALSE" + } + ], + "hitPolicy": "F", + "ruleList": [ + [ + "<= 1000", + "\"PERSONAL_LOAN\"", + "> 6", + "amount", + "TRUE" + ], + [ + "<=1000", + "\"PERSONAL_LOAN\"", + "-", + "if amount < (monthlyIncome*6) then amount else (monthlyIncome*6)", + "FALSE" + ], + [ + "> 1000", + "\"PERSONAL_LOAN\"", + "-", + "if amount < (monthlyIncome*6) then amount else (monthlyIncome*6)", + "FALSE" + ], + [ + "-", + "\"HOME_LOAN\"", + "-", + "if amount < (monthlyIncome*72) then amount else (monthlyIncome*72)", + "FALSE" + ], + [ + "-", + "\"CAR_LOAN\"", + "-", + "if amount < (monthlyIncome*12) then amount else (monthlyIncome*12)", + "FALSE" + ] + ] + }, + "id": "5e32af1955bfd8ff5423a44a", + "_version": "9be0913d-f095-4421-a547-62b0c1487241" + }, + { + "name": "eligibility_FR", + "decisionRules": "{\"inputExpressionList\":[\"amount\",\"type\",\"experience\",\"employmentType\"],\"outputs\":[\"eligibility\",\"preApproved\"],\"hitPolicy\":\"F\",\"ruleList\":[[\"<= 2000\",\"\\\"PERSONAL_LOAN\\\"\",\"> 6\",\"\\\"FULL_TIME\\\"\",\"amount\",\"TRUE\"],[\"<=2000\",\"\\\"PERSONAL_LOAN\\\"\",\"-\",\"\\\"FULL_TIME\\\"\",\"if amount < (monthlyIncome*6 + totalOtherIncome*2) then amount else (monthlyIncome*6 + totalOtherIncome*2)\",\"FALSE\"],[\"> 2000\",\"\\\"PERSONAL_LOAN\\\"\",\"-\",\"\\\"FULL_TIME\\\"\",\"if amount < (monthlyIncome*6 + totalOtherIncome*2) then amount else (monthlyIncome*6 + totalOtherIncome*2)\",\"FALSE\"],[\"-\",\"\\\"PERSONAL_LOAN\\\"\",\"-\",\"\\\"PART_TIME\\\"\",\"if amount < (monthlyIncome*2 + totalOtherIncome*2) then amount else (monthlyIncome*2 + totalOtherIncome*2)\",\"FALSE\"],[\"-\",\"\\\"PERSONAL_LOAN\\\"\",\"-\",\"\\\"SELF_EMPLOYED\\\"\",\"if amount < (monthlyIncome + totalOtherIncome*2) then amount else (monthlyIncome + totalOtherIncome*2)\",\"FALSE\"],[\"-\",\"\\\"CAR_LOAN\\\"\",\"-\",\"\\\"FULL_TIME\\\"\",\"if amount < 6*(monthlyIncome*6 + totalOtherIncome*2) then amount else 6*(monthlyIncome*6 + totalOtherIncome*2)\",\"FALSE\"],[\"-\",\"\\\"CAR_LOAN\\\"\",\"-\",\"\\\"PART_TIME\\\"\",\"if amount < 6*(monthlyIncome*4 + totalOtherIncome*2) then amount else 6*(monthlyIncome*4 + totalOtherIncome*2)\",\"FALSE\"],[\"-\",\"\\\"CAR_LOAN\\\"\",\"-\",\"\\\"SELF_EMPLOYED\\\"\",\"if amount < 6*(monthlyIncome*2 + totalOtherIncome*2) then amount else 6*(monthlyIncome*2 + totalOtherIncome*2)\",\"FALSE\"],[\"-\",\"\\\"HOME_LOAN\\\"\",\"-\",\"\\\"FULL_TIME\\\"\",\"if amount < 12*(monthlyIncome*6 + totalOtherIncome*2) then amount else 12*(monthlyIncome*6 + totalOtherIncome*2)\",\"FALSE\"],[\"-\",\"\\\"HOME_LOAN\\\"\",\"-\",\"\\\"PART_TIME\\\"\",\"if amount < 12*(monthlyIncome*4 + totalOtherIncome*2) then amount else 12*(monthlyIncome*4 + totalOtherIncome*2)\",\"FALSE\"],[\"-\",\"\\\"HOME_LOAN\\\"\",\"-\",\"\\\"SELF_EMPLOYED\\\"\",\"if amount < 12*(monthlyIncome*2 + totalOtherIncome*2) then amount else 12*(monthlyIncome*2 + totalOtherIncome*2)\",\"FALSE\"]],\"inputValues\":[\"<= 2000,<=2000,> 2000,-\",\"\\\"PERSONAL_LOAN\\\",\\\"CAR_LOAN\\\",\\\"HOME_LOAN\\\"\",\"> 6,-\",\"\\\"FULL_TIME\\\",\\\"PART_TIME\\\",\\\"SELF_EMPLOYED\\\"\"],\"outputValues\":[\"amount,if amount < (monthlyIncome*6 + totalOtherIncome*2) then amount else (monthlyIncome*6 + totalOtherIncome*2),if amount < (monthlyIncome*2 + totalOtherIncome*2) then amount else (monthlyIncome*2 + totalOtherIncome*2),if amount < (monthlyIncome + totalOtherIncome*2) then amount else (monthlyIncome + totalOtherIncome*2),if amount < 6*(monthlyIncome*6 + totalOtherIncome*2) then amount else 6*(monthlyIncome*6 + totalOtherIncome*2),if amount < 6*(monthlyIncome*4 + totalOtherIncome*2) then amount else 6*(monthlyIncome*4 + totalOtherIncome*2),if amount < 6*(monthlyIncome*2 + totalOtherIncome*2) then amount else 6*(monthlyIncome*2 + totalOtherIncome*2),if amount < 12*(monthlyIncome*6 + totalOtherIncome*2) then amount else 12*(monthlyIncome*6 + totalOtherIncome*2),if amount < 12*(monthlyIncome*4 + totalOtherIncome*2) then amount else 12*(monthlyIncome*4 + totalOtherIncome*2),if amount < 12*(monthlyIncome*2 + totalOtherIncome*2) then amount else 12*(monthlyIncome*2 + totalOtherIncome*2)\",\"TRUE,FALSE\"],\"context\":null}", + "dtDesignerData": { + "name": "", + "inputExpressionList": [ + { + "label": "amount", + "expr": "amount", + "type": "string", + "values": "<= 2000,<=2000,> 2000,-" + }, + { + "label": "type", + "expr": "type", + "type": "string", + "values": "\"PERSONAL_LOAN\",\"CAR_LOAN\",\"HOME_LOAN\"" + }, + { + "label": "experience", + "expr": "experience", + "type": "string", + "values": "> 6,-" + }, + { + "label": "employmentType", + "expr": "employmentType", + "type": "string", + "values": "\"FULL_TIME\",\"PART_TIME\",\"SELF_EMPLOYED\"" + } + ], + "outputs": [ + { + "label": "eligibility", + "expr": "eligibility", + "type": "string", + "values": "amount,if amount < (monthlyIncome*6 + totalOtherIncome*2) then amount else (monthlyIncome*6 + totalOtherIncome*2),if amount < (monthlyIncome*2 + totalOtherIncome*2) then amount else (monthlyIncome*2 + totalOtherIncome*2),if amount < (monthlyIncome + totalOtherIncome*2) then amount else (monthlyIncome + totalOtherIncome*2),if amount < 6*(monthlyIncome*6 + totalOtherIncome*2) then amount else 6*(monthlyIncome*6 + totalOtherIncome*2),if amount < 6*(monthlyIncome*4 + totalOtherIncome*2) then amount else 6*(monthlyIncome*4 + totalOtherIncome*2),if amount < 6*(monthlyIncome*2 + totalOtherIncome*2) then amount else 6*(monthlyIncome*2 + totalOtherIncome*2),if amount < 12*(monthlyIncome*6 + totalOtherIncome*2) then amount else 12*(monthlyIncome*6 + totalOtherIncome*2),if amount < 12*(monthlyIncome*4 + totalOtherIncome*2) then amount else 12*(monthlyIncome*4 + totalOtherIncome*2),if amount < 12*(monthlyIncome*2 + totalOtherIncome*2) then amount else 12*(monthlyIncome*2 + totalOtherIncome*2)" + }, + { + "label": "preApproved", + "expr": "preApproved", + "type": "string", + "values": "TRUE,FALSE" + } + ], + "hitPolicy": "F", + "ruleList": [ + [ + "<= 2000", + "\"PERSONAL_LOAN\"", + "> 6", + "\"FULL_TIME\"", + "amount", + "TRUE" + ], + [ + "<=2000", + "\"PERSONAL_LOAN\"", + "-", + "\"FULL_TIME\"", + "if amount < (monthlyIncome*6 + totalOtherIncome*2) then amount else (monthlyIncome*6 + totalOtherIncome*2)", + "FALSE" + ], + [ + "> 2000", + "\"PERSONAL_LOAN\"", + "-", + "\"FULL_TIME\"", + "if amount < (monthlyIncome*6 + totalOtherIncome*2) then amount else (monthlyIncome*6 + totalOtherIncome*2)", + "FALSE" + ], + [ + "-", + "\"PERSONAL_LOAN\"", + "-", + "\"PART_TIME\"", + "if amount < (monthlyIncome*2 + totalOtherIncome*2) then amount else (monthlyIncome*2 + totalOtherIncome*2)", + "FALSE" + ], + [ + "-", + "\"PERSONAL_LOAN\"", + "-", + "\"SELF_EMPLOYED\"", + "if amount < (monthlyIncome + totalOtherIncome*2) then amount else (monthlyIncome + totalOtherIncome*2)", + "FALSE" + ], + [ + "-", + "\"CAR_LOAN\"", + "-", + "\"FULL_TIME\"", + "if amount < 6*(monthlyIncome*6 + totalOtherIncome*2) then amount else 6*(monthlyIncome*6 + totalOtherIncome*2)", + "FALSE" + ], + [ + "-", + "\"CAR_LOAN\"", + "-", + "\"PART_TIME\"", + "if amount < 6*(monthlyIncome*4 + totalOtherIncome*2) then amount else 6*(monthlyIncome*4 + totalOtherIncome*2)", + "FALSE" + ], + [ + "-", + "\"CAR_LOAN\"", + "-", + "\"SELF_EMPLOYED\"", + "if amount < 6*(monthlyIncome*2 + totalOtherIncome*2) then amount else 6*(monthlyIncome*2 + totalOtherIncome*2)", + "FALSE" + ], + [ + "-", + "\"HOME_LOAN\"", + "-", + "\"FULL_TIME\"", + "if amount < 12*(monthlyIncome*6 + totalOtherIncome*2) then amount else 12*(monthlyIncome*6 + totalOtherIncome*2)", + "FALSE" + ], + [ + "-", + "\"HOME_LOAN\"", + "-", + "\"PART_TIME\"", + "if amount < 12*(monthlyIncome*4 + totalOtherIncome*2) then amount else 12*(monthlyIncome*4 + totalOtherIncome*2)", + "FALSE" + ], + [ + "-", + "\"HOME_LOAN\"", + "-", + "\"SELF_EMPLOYED\"", + "if amount < 12*(monthlyIncome*2 + totalOtherIncome*2) then amount else 12*(monthlyIncome*2 + totalOtherIncome*2)", + "FALSE" + ] + ] + }, + "id": "5e32af7c55bfd8ff5423a44c", + "_version": "3cb2aaf6-3b45-4922-9642-30bee2fff72e" + }, + { + "name": "UserLocation", + "decisionRules": "{\"inputExpressionList\":[\"userName\"],\"outputs\":[\"location\"],\"hitPolicy\":\"U\",\"ruleList\":[[\"\\\"user1\\\"\",\"\\\"US\\\"\"],[\"\\\"user2\\\"\",\"\\\"FR\\\"\"]],\"inputValues\":[\"\"],\"outputValues\":[\"\"],\"context\":null}", + "dtDesignerData": { + "name": "", + "inputExpressionList": [ + { + "label": "userName", + "expr": "userName", + "type": "string", + "values": "" + } + ], + "outputs": [ + { + "label": "location", + "expr": "location", + "type": "string", + "values": "" + } + ], + "hitPolicy": "U", + "ruleList": [ + [ + "\"user1\"", + "\"US\"" + ], + [ + "\"user2\"", + "\"FR\"" + ] + ] + }, + "id": "5e3be26b88e4540c0923d769", + "_oldVersion": "417966e9-807d-4552-a9b9-eb6b13c28c6b", + "_version": "5de5a8ec-3fc7-40c1-b93d-d2db3efd6139" + } +] \ No newline at end of file diff --git a/test/bpmn-files/DecisionTreeData.json b/test/bpmn-files/DecisionTreeData.json new file mode 100644 index 0000000..ed07b8c --- /dev/null +++ b/test/bpmn-files/DecisionTreeData.json @@ -0,0 +1,61 @@ +{ + "name": "sampleTree", + "nodes": [ + { + "id": "n-rkljn4byr", + "name": "UserLocation", + "nodeType": "DECISION_TABLE", + "x": 574, + "y": 8, + "data": {}, + "skipFeel": true + }, + { + "id": "n-fub5yhz6f", + "name": "Decision Gate 2", + "nodeType": "DECISION_GATE", + "x": 574, + "y": 168, + "data": {}, + "skipFeel": true + }, + { + "id": "n-rze21y6nh", + "name": "eligibility_USA", + "nodeType": "DECISION_TABLE", + "x": 327, + "y": 425, + "data": {}, + "skipFeel": true + }, + { + "id": "n-rbnxnuxst", + "name": "eligibility_FR", + "nodeType": "DECISION_TABLE", + "x": 856, + "y": 432, + "data": {}, + "skipFeel": true + } + ], + "connections": [ + { + "from": "n-rkljn4byr", + "to": "n-fub5yhz6f", + "id": "c-1ghbvmu02", + "condition": "" + }, + { + "from": "n-fub5yhz6f", + "to": "n-rze21y6nh", + "id": "c-ebty0cye", + "condition": "location === \"US\"" + }, + { + "from": "n-fub5yhz6f", + "to": "n-rbnxnuxst", + "id": "c-appxa5hi2u", + "condition": "location == \"FR\"" + } + ] +} \ No newline at end of file diff --git a/test/bpmn-files/Service.xlsx b/test/bpmn-files/Service.xlsx new file mode 100644 index 0000000..00dc706 Binary files /dev/null and b/test/bpmn-files/Service.xlsx differ diff --git a/test/bpmn-files/business-rule-decision-service.bpmn b/test/bpmn-files/business-rule-decision-service.bpmn new file mode 100644 index 0000000..830139d --- /dev/null +++ b/test/bpmn-files/business-rule-decision-service.bpmn @@ -0,0 +1,88 @@ + + + + + SequenceFlow_1pm4fpm + + + + SequenceFlow_1rae5vs + + + + + + { + Age: 51, + MaritalStatus: 'M', + EmploymentStatus: 'EMPLOYED', + ExistingCustomer: false, + Monthly: { + Income: pv('income'), + Repayments: 2500, + Expenses: 3000 + } +} + + + + STANDARD LOAN + 0.08 + 100000 + 36 + + + + + false + 600 + + + + + SequenceFlow_1pm4fpm + SequenceFlow_11pp2es + + + + + SequenceFlow_11pp2es + SequenceFlow_1rae5vs + _setPV("ruleEngineDecision",_msg) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/bpmn-files/business-rule-task.bpmn b/test/bpmn-files/business-rule-decision-table.bpmn similarity index 85% rename from test/bpmn-files/business-rule-task.bpmn rename to test/bpmn-files/business-rule-decision-table.bpmn index 6210cbb..79d2cb9 100644 --- a/test/bpmn-files/business-rule-task.bpmn +++ b/test/bpmn-files/business-rule-decision-table.bpmn @@ -1,5 +1,5 @@ - + SequenceFlow_11pta42 @@ -10,7 +10,7 @@ SequenceFlow_1gaxaje - + Private @@ -24,7 +24,7 @@ Storing response to process variable SequenceFlow_1d015z6 SequenceFlow_1gaxaje - + _setPV("ruleEngineDecision",_msg) @@ -36,15 +36,15 @@ - - + + - - + + @@ -56,8 +56,8 @@ - - + + diff --git a/test/bpmn-files/business-rule-decision-tree.bpmn b/test/bpmn-files/business-rule-decision-tree.bpmn new file mode 100644 index 0000000..af52707 --- /dev/null +++ b/test/bpmn-files/business-rule-decision-tree.bpmn @@ -0,0 +1,66 @@ + + + + + SequenceFlow_1pm4fpm + + + + SequenceFlow_1rae5vs + + + + + user1 + 3000 + PERSONAL_LOAN + 5 + 1000 + + + SequenceFlow_1pm4fpm + SequenceFlow_11pp2es + + + + + SequenceFlow_11pp2es + SequenceFlow_1rae5vs + _setPV("ruleEngineDecision",_msg) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/bpmn-files/conditional-boundary-interrupting.bpmn b/test/bpmn-files/conditional-boundary-interrupting.bpmn new file mode 100644 index 0000000..cfee5c7 --- /dev/null +++ b/test/bpmn-files/conditional-boundary-interrupting.bpmn @@ -0,0 +1,157 @@ + + + + + SequenceFlow_15i1udn + + + SequenceFlow_0txxvqc + SequenceFlow_1p3c852 + + + SequenceFlow_1uf7915 + SequenceFlow_0132k3f + + + SequenceFlow_0132k3f + + + + SequenceFlow_0mfsmbj + SequenceFlow_1oi57n2 + setPV('test2', 'skipped'); + + + SequenceFlow_1oi57n2 + + + + + SequenceFlow_15i1udn + SequenceFlow_0txxvqc + SequenceFlow_1uf7915 + + + + + SequenceFlow_0mfsmbj + + ${pv.condition === true} + + + + + + SequenceFlow_0fpeo13 + + + + SequenceFlow_1p3c852 + SequenceFlow_0fpeo13 + setPV('condition',true) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/bpmn-files/conditional-boundary-non-interrupting.bpmn b/test/bpmn-files/conditional-boundary-non-interrupting.bpmn new file mode 100644 index 0000000..2643a56 --- /dev/null +++ b/test/bpmn-files/conditional-boundary-non-interrupting.bpmn @@ -0,0 +1,157 @@ + + + + + SequenceFlow_15i1udn + + + SequenceFlow_0txxvqc + SequenceFlow_1p3c852 + + + SequenceFlow_1uf7915 + SequenceFlow_0132k3f + + + SequenceFlow_0132k3f + + + + SequenceFlow_0mfsmbj + SequenceFlow_1oi57n2 + setPV('test2', 'done'); + + + SequenceFlow_1oi57n2 + + + + + SequenceFlow_15i1udn + SequenceFlow_0txxvqc + SequenceFlow_1uf7915 + + + + + + + SequenceFlow_0fpeo13 + + + + SequenceFlow_1p3c852 + SequenceFlow_0fpeo13 + setPV('condition',true) + + + SequenceFlow_0mfsmbj + + ${pv.condition === true} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/bpmn-files/link-event.bpmn b/test/bpmn-files/link-event.bpmn new file mode 100644 index 0000000..1bbec2a --- /dev/null +++ b/test/bpmn-files/link-event.bpmn @@ -0,0 +1,272 @@ + + + + + SequenceFlow_004zi2f + + + + + SequenceFlow_004zi2f + SequenceFlow_1eaudk5 + + + SequenceFlow_1eaudk5 + + + + SequenceFlow_0mp9sfb + SequenceFlow_0bkauxz + SequenceFlow_1kxecrb + + + + + + + SequenceFlow_0mp9sfb + SequenceFlow_143uim4 + var msg = pv('msgA'); +var a = 1; +var b = 2; +var add = a + b; +setPV('addA', add); +sendMsg({textA: msg.toUpperCase(), resultA: add}); + + + SequenceFlow_0bkauxz + SequenceFlow_1k8uduf + var msg = pv('msgB'); +var a = 3; +var b = 4; +var add = a + b; +setPV('addB', add); +sendMsg({textB: msg.toUpperCase(), resultB: add}); + + + SequenceFlow_1kxecrb + SequenceFlow_1se9rod + var msg = pv('msgC'); +var a = 5; +var b = 6; +var add = a + b; +setPV('addC', add); +sendMsg({textC: msg.toUpperCase(), resultC: add}); + + + + + + SequenceFlow_143uim4 + + + + SequenceFlow_1k8uduf + + + + SequenceFlow_1se9rod + + + + SequenceFlow_1opm1a4 + + + + SequenceFlow_0d8g872 + + + + SequenceFlow_146lmle + + + + + + + SequenceFlow_1qcpyru + + + + SequenceFlow_0d8g872 + SequenceFlow_0ef4i9x + setPV('scriptB', 'done'); + + + SequenceFlow_0ef4i9x + + + + SequenceFlow_1h8lhfz + + + + SequenceFlow_146lmle + SequenceFlow_1h8lhfz + setPV('scriptC', 'done'); + + + SequenceFlow_1opm1a4 + SequenceFlow_1qcpyru + setPV('scriptA', 'done'); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/bpmn-files/user-task-hook.bpmn b/test/bpmn-files/user-task-hook.bpmn index 95ec4ca..42c56bc 100644 --- a/test/bpmn-files/user-task-hook.bpmn +++ b/test/bpmn-files/user-task-hook.bpmn @@ -1,5 +1,5 @@ - + SequenceFlow_1a2aang @@ -9,7 +9,7 @@ SequenceFlow_1belike - + SequenceFlow_1uyniua SequenceFlow_0j1vgm7 @@ -20,16 +20,10 @@ SequenceFlow_07nhzj7 SequenceFlow_1gzfes6 - - SequenceFlow_0j1vgm7 - SequenceFlow_0qds1mo - SequenceFlow_1fnupjk - SequenceFlow_1belike - - + SequenceFlow_07nhzj7 SequenceFlow_0qds1mo @@ -39,6 +33,12 @@ SequenceFlow_1fnupjk + + SequenceFlow_0j1vgm7 + SequenceFlow_0qds1mo + SequenceFlow_1fnupjk + SequenceFlow_1belike + @@ -49,8 +49,8 @@ - - + + @@ -62,9 +62,9 @@ - - - + + + @@ -73,9 +73,9 @@ - - - + + + @@ -86,31 +86,25 @@ - - - - - - - - + + - - - + + + - - - + + + @@ -119,9 +113,9 @@ - - - + + + @@ -130,13 +124,19 @@ - - - + + + + + + + + + diff --git a/test/config.json b/test/config.json index fdc2f23..166d2b3 100644 --- a/test/config.json +++ b/test/config.json @@ -26,7 +26,9 @@ "addonModule": "test/utils/addon-functions", "recovery": { "retryInterval": 2000 - } + }, + "disableMakerCheckerBeforeSave": false, + "disableMakerCheckerParallelValidations": false }, "masterJobExecutor": { "initDelay": 1000, diff --git a/test/scripts/activiti-integration-tests.js b/test/scripts/activiti-integration-tests.js index b047b59..c5e1461 100644 --- a/test/scripts/activiti-integration-tests.js +++ b/test/scripts/activiti-integration-tests.js @@ -49,7 +49,7 @@ describe('Activiti Integration Tests', function CB() { let url = activitiHost + '/activiti-rest/service/'; - ActivitiManager.enable(url, bootstrap.getContext('usr1'), function cb(err, res) { + ActivitiManager.enable({baseUrl:url}, bootstrap.getContext('usr1'), function cb(err, res) { expect(err).to.not.exist; expect(res).to.exist; done(); diff --git a/test/scripts/business-rule-task-tests.js b/test/scripts/business-rule-task-tests.js index 2f12279..3b95cd0 100644 --- a/test/scripts/business-rule-task-tests.js +++ b/test/scripts/business-rule-task-tests.js @@ -12,80 +12,244 @@ let Status = bootstrap.Status; let stateVerifier = require('../utils/state-verifier'); let fs = require('fs'); var path = require('path'); +var decisionTableData = require('../bpmn-files/DecisionTableData.json'); +var decisionTreeData = require('../bpmn-files/DecisionTreeData.json'); -describe('Business Rule Node', function CB() { - let workflowName = 'business-rule-task'; +describe('Business Rule Node', function CB(){ - before('define workflow', function testFunction(done) { - /* Sometimes Oracle takes time */ - this.timeout(60000); - let fileContents = ''; - var readStream = fs.createReadStream(path.resolve('./test/bpmn-files/Adjustments.xlsx'), 'base64'); - readStream.on('data', function dataCb(chunk) { - fileContents += chunk; - }).on('end', function endCb() { - bootstrap.app.models.DecisionTable.create({ - name: 'Adjustments', - documentName: 'Adjustments.xlsx', - documentData: 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,' + fileContents - }, bootstrap.defaultContext, function cb(err, data) { - expect(err).to.not.exist; - expect(data).to.exist.and.have.property('decisionRules').that.exists; + describe('Decision Table Tests', function CB() { + let workflowName = 'business-rule-decision-table'; + + before('define workflow', function testFunction(done) { + /* Sometimes Oracle takes time */ + this.timeout(60000); + let fileContents = ''; + var readStream = fs.createReadStream(path.resolve('./test/bpmn-files/Adjustments.xlsx'), 'base64'); + readStream.on('data', function dataCb(chunk) { + fileContents += chunk; + }).on('end', function endCb() { + bootstrap.app.models.DecisionTable.create({ + name: 'Adjustments', + documentName: 'Adjustments.xlsx', + documentData: 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,' + fileContents + }, bootstrap.defaultContext, function cb(err, data) { + expect(err).to.not.exist; + expect(data).to.exist.and.have.property('decisionRules').that.exists; - bootstrap.loadBpmnFile(workflowName, function testFunction(err) { - done(err); + bootstrap.loadBpmnFile(workflowName, function testFunction(err) { + done(err); + }); }); }); }); - }); - after('Cleanup data', function testFunction(done) { - bootstrap.app.models.DecisionTable.destroyAll({}, bootstrap.defaultContext, function cb(err) { - expect(err).to.not.exist; - bootstrap.cleanUp(workflowName, done); + after('Cleanup data', function testFunction(done) { + bootstrap.app.models.DecisionTable.destroyAll({}, bootstrap.defaultContext, function cb(err) { + expect(err).to.not.exist; + bootstrap.cleanUp(workflowName, done); + }); + }); + + it('errors when underlying decision-table is not defined', function cb(done) { + bootstrap.triggerAndWaitForTokenStatus(workflowName, { + processVariables: { + ruleName: 'XAdjustments' + } + }, 'BusinessRule Task', Status.FAILED, function testFunction(err, wfInst, procInst, token) { + expect(err).to.not.exist; + expect(procInst).to.exist; + stateVerifier.isRunning(procInst); + stateVerifier.verifyTokens(procInst, ['Start', { + name: 'BusinessRule Task', + status: Status.FAILED + }]); + + expect(token.status).to.equal(Status.FAILED); + expect(token.error).to.exist; + expect(token.error.message).to.equal('No Document found for DocumentName XAdjustments'); + done(); + }); + }); + + it('executes the rule and passes results as message to next node', function testFunction(done) { + bootstrap.triggerAndComplete(workflowName, { + processVariables: { + ruleName: 'Adjustments' + } + }, function testFunction(err, wfInst, procInst) { + expect(err).to.not.exist; + expect(procInst).to.exist; + stateVerifier.isComplete(procInst); + stateVerifier.verifyTokens(procInst, ['Start', 'BusinessRule Task', 'Script Task', 'End']); + expect(procInst._processVariables).to.exist; + expect(procInst._processVariables.ruleEngineDecision).to.exist; + expect(procInst._processVariables.ruleEngineDecision.output).to.exist; + expect(procInst._processVariables.ruleEngineDecision.output).to.deep.equal({ + // Customer: 'Private', + // OrderSize: 25, + Discount: 0.05, + Shipping: 'Air' + }); + done(); + }); }); }); - it('errors when underlying decision-table is not defined', function cb(done) { - bootstrap.triggerAndWaitForTokenStatus(workflowName, { - processVariables: { - ruleName: 'XAdjustments' - } - }, 'BusinessRule Task', Status.FAILED, function testFunction(err, wfInst, procInst, token) { - expect(err).to.not.exist; - expect(procInst).to.exist; - stateVerifier.isRunning(procInst); - stateVerifier.verifyTokens(procInst, ['Start', { - name: 'BusinessRule Task', - status: Status.FAILED - }]); + describe('Decision Service Tests', function CB() { + let workflowName = 'business-rule-decision-service'; - expect(token.status).to.equal(Status.FAILED); - expect(token.error).to.exist; - expect(token.error.message).to.equal('No Document found for DocumentName XAdjustments'); - done(); + before('define workflow', function testFunction(done) { + /* Sometimes Oracle takes time */ + this.timeout(60000); + let fileContents = ''; + var readStream = fs.createReadStream(path.resolve('./test/bpmn-files/Service.xlsx'), 'base64'); + readStream.on('data', function dataCb(chunk) { + fileContents += chunk; + }).on('end', function endCb() { + bootstrap.app.models.DecisionGraph.create({ + name: 'test', + documentName: 'Service.xlsx', + documentData: 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,' + fileContents + }, bootstrap.defaultContext, function cb(err, data) { + expect(err).to.not.exist; + expect(data).to.exist.and.have.property('data').that.exists; + bootstrap.app.models.DecisionService.create({ + name: 'service1', + decisions: ['Routing'], + graphId: 'test' + }, bootstrap.defaultContext, function cb(err, data){ + expect(err).to.not.exist; + expect(data).to.exist.and.have.property('id').that.exists; + bootstrap.loadBpmnFile(workflowName, function testFunction(err) { + done(err); + }); + }) + }); + }); + }); + after('Cleanup data', function testFunction(done) { + bootstrap.app.models.DecisionGraph.destroyAll({}, bootstrap.defaultContext, function cb(err) { + expect(err).to.not.exist; + bootstrap.app.models.DecisionService.destroyAll({}, bootstrap.defaultContext, function cb(err) { + expect(err).to.not.exist; + bootstrap.cleanUp(workflowName, done); + }); + }); + }); + + it('errors when underlying decision-service is not defined', function cb(done) { + bootstrap.triggerAndWaitForTokenStatus(workflowName, { + processVariables: { + serviceName: 'wrongService', + income: 10000 + } + }, 'BusinessRule Task', Status.FAILED, function testFunction(err, wfInst, procInst, token) { + expect(err).to.not.exist; + expect(procInst).to.exist; + stateVerifier.isRunning(procInst); + stateVerifier.verifyTokens(procInst, ['Start', { + name: 'BusinessRule Task', + status: Status.FAILED + }]); + expect(token.status).to.equal(Status.FAILED); + expect(token.error).to.exist; + expect(token.error.message).to.equal('No Service found for ServiceName wrongService'); + done(); + }); + }); + + it('invokes the service and passes results as message to next node', function testFunction(done) { + bootstrap.triggerAndComplete(workflowName, { + processVariables: { + serviceName: 'service1', + income: 10000 + } + }, function testFunction(err, wfInst, procInst) { + expect(err).to.not.exist; + expect(procInst).to.exist; + stateVerifier.isComplete(procInst); + stateVerifier.verifyTokens(procInst, ['Start', 'BusinessRule Task', 'Script Task', 'End']); + expect(procInst._processVariables).to.exist; + expect(procInst._processVariables.ruleEngineDecision).to.exist; + expect(procInst._processVariables.ruleEngineDecision.output).to.exist; + expect(procInst._processVariables.ruleEngineDecision.output.Routing).to.exist; + expect(procInst._processVariables.ruleEngineDecision.output.Routing).to.deep.equal({ + Routing: 'ACCEPT' + }); + done(); + }); }); }); - it('executes the rule and passes results as message to next node', function testFunction(done) { - bootstrap.triggerAndComplete(workflowName, { - processVariables: { - ruleName: 'Adjustments' - } - }, function testFunction(err, wfInst, procInst) { - expect(err).to.not.exist; - expect(procInst).to.exist; - stateVerifier.isComplete(procInst); - stateVerifier.verifyTokens(procInst, ['Start', 'BusinessRule Task', 'Script Task', 'End']); - expect(procInst._processVariables).to.exist; - expect(procInst._processVariables.ruleEngineDecision).to.exist; - expect(procInst._processVariables.ruleEngineDecision.body).to.exist; - expect(procInst._processVariables.ruleEngineDecision.body).to.deep.equal({ - // Customer: 'Private', - // OrderSize: 25, - Discount: 0.05, - Shipping: 'Air' + describe('Decision Tree Tests', function CB() { + let workflowName = 'business-rule-decision-tree'; + + before('define workflow', function testFunction(done) { + /* Sometimes Oracle takes time */ + this.timeout(60000); + bootstrap.app.models.DecisionTable.create(decisionTableData, bootstrap.defaultContext, function cb(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + bootstrap.app.models.DecisionTree.create(decisionTreeData, bootstrap.defaultContext, function cb(err, data){ + expect(err).to.not.exist; + expect(data).to.exist.and.have.property('id').that.exists; + bootstrap.loadBpmnFile(workflowName, function testFunction(err) { + done(err); + }); + }) + }); + }); + + after('Cleanup data', function testFunction(done) { + bootstrap.app.models.DecisionTable.destroyAll({}, bootstrap.defaultContext, function cb(err) { + expect(err).to.not.exist; + bootstrap.app.models.DecisionTree.destroyAll({}, bootstrap.defaultContext, function cb(err) { + expect(err).to.not.exist; + bootstrap.cleanUp(workflowName, done); + }); + }); + }); + + it('errors when underlying decision-tree is not defined', function cb(done) { + bootstrap.triggerAndWaitForTokenStatus(workflowName, { + processVariables: { + treeName: 'wrongTree' + } + }, 'BusinessRule Task', Status.FAILED, function testFunction(err, wfInst, procInst, token) { + expect(err).to.not.exist; + expect(procInst).to.exist; + stateVerifier.isRunning(procInst); + stateVerifier.verifyTokens(procInst, ['Start', { + name: 'BusinessRule Task', + status: Status.FAILED + }]); + expect(token.status).to.equal(Status.FAILED); + expect(token.error).to.exist; + expect(token.error.message).to.equal('No Tree found for TreeName wrongTree'); + done(); + }); + }); + + it('executes the tree and passes results as message to next node', function testFunction(done) { + bootstrap.triggerAndComplete(workflowName, { + processVariables: { + treeName: 'sampleTree' + } + }, function testFunction(err, wfInst, procInst) { + expect(err).to.not.exist; + expect(procInst).to.exist; + stateVerifier.isComplete(procInst); + stateVerifier.verifyTokens(procInst, ['Start', 'BusinessRule Task', 'Script Task', 'End']); + expect(procInst._processVariables).to.exist; + expect(procInst._processVariables.ruleEngineDecision).to.exist; + expect(procInst._processVariables.ruleEngineDecision.output).to.exist; + expect(procInst._processVariables.ruleEngineDecision.output).to.deep.equal({ + location: 'US', + preApproved: false, + eligibility: 3000 + }); + done(); }); - done(); }); }); -}); + +}); \ No newline at end of file diff --git a/test/scripts/conditional-boundary-interrupting-tests.js b/test/scripts/conditional-boundary-interrupting-tests.js new file mode 100644 index 0000000..8469cf7 --- /dev/null +++ b/test/scripts/conditional-boundary-interrupting-tests.js @@ -0,0 +1,119 @@ +/** + * + * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ +let async = require('async'); +let bootstrap = require('../bootstrap.js'); +let chai = bootstrap.chai; +let expect = chai.expect; +let Status = bootstrap.Status; +let stateVerifier = require('../utils/state-verifier'); + +describe('Conditional Event Boundary Interrupting Tests', function CB() { + let workflowName = 'conditional-boundary-interrupting'; + before('define workflow', function testFunction(done) { + bootstrap.loadBpmnFile(workflowName, function testFunction(err) { + done(err); + }); + }); + after('cleanup data', function testFunction(done) { + bootstrap.cleanUp(workflowName, done); + }); + + let test1; + let test2; + beforeEach(function testFunction(done) { + bootstrap.triggerWorkflow(workflowName, {}, function testFunction(err, wfInstance) { + expect(err).to.not.exist; + expect(wfInstance).to.exist; + }); + + async.parallel([ + function testFunction(callback) { + bootstrap.onUserTask(workflowName, 'Test1', function testFunction(err, task, instance) { + expect(err).to.not.exist; + test1 = task; + callback(); + }); + }, + function testFunction(callback) { + bootstrap.onUserTask(workflowName, 'Test2', function testFunction(err, task, instance) { + expect(err).to.not.exist; + test2 = task; + callback(); + }); + } + ], function testFunction(err, results) { + done(err); + }); + }); + afterEach(function testFunction(done) { + test1 = null; + test2 = null; + bootstrap.removeCompleteListener(workflowName); + bootstrap.removeTokenStatusListener(workflowName); + bootstrap.removeUserTaskListener(workflowName, 'Test1'); + bootstrap.removeUserTaskListener(workflowName, 'Test2'); + done(); + }); + + it('Conditional Boundary interrupts the Test2 Task when the condition (pv.condition === true) is met', function testFunction(done) { + expect(test1).to.exist; + test1.complete({}, bootstrap.defaultContext, function testFunction(err, task) { + expect(err).to.not.exist; + expect(task).to.exist.and.have.property('status').that.equals(Status.COMPLETE); + }); + bootstrap.onComplete(workflowName, function testFunction(err, instance) { + expect(err).to.not.exist; + expect(instance).to.exist; + expect(instance._processVariables.condition).to.exist; + expect(instance._processVariables.condition).to.equals(true); + expect(instance._processVariables.condition).to.exist; + expect(instance._processVariables.test2).to.equals('skipped'); + stateVerifier.isComplete(instance); + stateVerifier.verifyTokens(instance, ['Start', 'PGway', 'Test1', 'Script', 'End1', { + name: 'Test2', + status: Status.INTERRUPTED + }, 'ConditionalBoundary', 'ConditionalScript', 'End3']); + done(); + }); + }); + + + it('If Test2 Task is completed before the Conditional Boundary, Conditional Boundary gets Interrupted', function testFunction(done) { + expect(test1).to.exist; + expect(test2).to.exist; + + bootstrap.onComplete(workflowName, function testFunction(err, processInstance) { + expect(err).to.not.exist; + stateVerifier.isComplete(processInstance); + stateVerifier.verifyTokens(processInstance, ['Start', 'PGway', 'Test1', 'Script', 'End1', 'Test2', { + name: 'ConditionalBoundary', + status: Status.INTERRUPTED + }, 'End2']); + done(); + }); + + async.series([ + function testFunction(callback) { + test2.complete({}, bootstrap.defaultContext, function testFunction(err, task) { + expect(err).to.not.exist; + expect(task.status).to.equal(Status.COMPLETE); + callback(); + }); + }, + function testFunction(callback) { + test1.complete({}, bootstrap.defaultContext, function testFunction(err, task) { + expect(err).to.not.exist; + expect(task.status).to.equal(Status.COMPLETE); + callback(); + }); + } + ], function testFunction(err, results) { + expect(err).to.not.exist; + }); + }); +}); + diff --git a/test/scripts/conditional-boundary-non-interrupting-tests.js b/test/scripts/conditional-boundary-non-interrupting-tests.js new file mode 100644 index 0000000..f405b24 --- /dev/null +++ b/test/scripts/conditional-boundary-non-interrupting-tests.js @@ -0,0 +1,130 @@ +/** + * + * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ +let async = require('async'); +let bootstrap = require('../bootstrap.js'); +let chai = bootstrap.chai; +let expect = chai.expect; +let Status = bootstrap.Status; +let stateVerifier = require('../utils/state-verifier'); + +describe('Conditional Event Boundary Non-Interrupting Tests', function CB() { + let workflowName = 'conditional-boundary-non-interrupting'; + before('define workflow', function testFunction(done) { + bootstrap.loadBpmnFile(workflowName, function testFunction(err) { + done(err); + }); + }); + after('cleanup data', function testFunction(done) { + bootstrap.cleanUp(workflowName, done); + }); + + let test1; + let test2; + beforeEach(function testFunction(done) { + bootstrap.triggerWorkflow(workflowName, {}, function testFunction(err, wfInstance) { + expect(err).to.not.exist; + expect(wfInstance).to.exist; + }); + + async.parallel([ + function testFunction(callback) { + bootstrap.onUserTask(workflowName, 'Test1', function testFunction(err, task, instance) { + expect(err).to.not.exist; + test1 = task; + callback(); + }); + }, + function testFunction(callback) { + bootstrap.onUserTask(workflowName, 'Test2', function testFunction(err, task, instance) { + expect(err).to.not.exist; + test2 = task; + callback(); + }); + } + ], function testFunction(err, results) { + done(err); + }); + }); + afterEach(function testFunction(done) { + test1 = null; + test2 = null; + bootstrap.removeCompleteListener(workflowName); + bootstrap.removeTokenStatusListener(workflowName); + bootstrap.removeUserTaskListener(workflowName, 'Test1'); + bootstrap.removeUserTaskListener(workflowName, 'Test2'); + done(); + }); + + it('when the condition of Conditional Boundary (non-interrupting) satisfies, it creates a new path without interrupting Task2', function testFunction(done) { + expect(test1).to.exist; + expect(test2).to.exist; + + bootstrap.onTokenStatus(workflowName, 'End3', Status.COMPLETE, function testFunction(err, instance, token) { + expect(err).to.not.exist; + /* Conditional-Boundary path completed, Process should still be running and waiting on Test2 Task */ + expect(token.status).to.equal(Status.COMPLETE); + stateVerifier.isRunning(instance); + let test2Token = stateVerifier.fetchTokenByName(instance, 'Test2'); + expect(test2Token).to.exist.and.have.property('status').that.equals(Status.PENDING); + + /* Making sure we complete test2 task after End3 (and hence after conditional boundary is completed) */ + test2.complete({}, bootstrap.defaultContext, function testFunction(err, task) { + expect(err).to.not.exist; + expect(task.status).to.equal(Status.COMPLETE); + }); + }); + + /* Complete the Test1 Task so that ConditionalBoundary triggers */ + test1.complete({}, bootstrap.defaultContext, function testFunction(err, task) { + expect(err).to.not.exist; + expect(task.status).to.equal(Status.COMPLETE); + }); + + bootstrap.onComplete(workflowName, function testFunction(err, processInstance) { + expect(err).to.not.exist; + stateVerifier.isComplete(processInstance); + expect(processInstance._processVariables.test2).to.exist; + expect(processInstance._processVariables.test2).to.equals('done'); + stateVerifier.verifyTokens(processInstance, ['Start', 'PGway', 'Test1', 'Script', 'End1', 'ConditionalBoundary', 'ConditionalScript', 'End3', 'Test2', 'End2']); + done(); + }); +}); + + it('If Test2 Task is completed before the Conditional Boundary, Conditional Boundary gets Interrupted', function testFunction(done) { + expect(test1).to.exist; + expect(test2).to.exist; + + bootstrap.onComplete(workflowName, function testFunction(err, processInstance) { + expect(err).to.not.exist; + stateVerifier.isComplete(processInstance); + stateVerifier.verifyTokens(processInstance, ['Start', 'PGway', 'Test1', 'Script', 'End1', 'Test2', { + name: 'ConditionalBoundary', + status: Status.INTERRUPTED + }, 'End2']); + done(); + }); + + async.series([ + function testFunction(callback) { + test2.complete({}, bootstrap.defaultContext, function testFunction(err, task) { + expect(err).to.not.exist; + expect(task.status).to.equal(Status.COMPLETE); + callback(); + }); + }, + function testFunction(callback) { + test1.complete({}, bootstrap.defaultContext, function testFunction(err, task) { + expect(err).to.not.exist; + expect(task.status).to.equal(Status.COMPLETE); + callback(); + }); + } + ], function testFunction(err, results) { + expect(err).to.not.exist; + }); + }); +}); diff --git a/test/scripts/link-event-tests.js b/test/scripts/link-event-tests.js new file mode 100644 index 0000000..957e38a --- /dev/null +++ b/test/scripts/link-event-tests.js @@ -0,0 +1,82 @@ +/** + * + * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ + +let bootstrap = require('../bootstrap.js'); +let chai = bootstrap.chai; +let expect = chai.expect; +let stateVerifier = require('../utils/state-verifier'); + +describe('Link Event Tests', function CB() { + let instance; + let workflowName = 'link-event'; + let workflowPayload = { + processVariables: { + "msgA": "hi to link a", + "msgB": "hi to link b", + "msgC": "hi to link c" + } + } + + before('define workflow', function testFunction(done) { + bootstrap.loadAndTrigger(workflowName, workflowPayload, function testFunction(err, wfDefn, wfInst) { + expect(err).to.not.exist; + expect(wfDefn).to.exist; + expect(wfInst).to.exist; + done(); + }); + }); + + after('cleanup data', function testFunction(done) { + bootstrap.removeCompleteListener(workflowName); + bootstrap.cleanUp(workflowName, done); + }); + + it('Link Events Passes the Flow from one Node to other Node and Completes the workflow', function testFunction(done) { + bootstrap.onComplete(workflowName, function testFunction(err, inst) { + expect(err).to.not.exist; + expect(inst).to.exist; + instance = inst; + stateVerifier.isComplete(instance); + stateVerifier.verifyTokens(instance, ['Start', 'PGIn', 'ScriptA', 'ScriptB', 'ScriptC', 'ScriptA2', 'ScriptB2', 'ScriptC2', 'EndA', 'EndB', 'EndC']); + expect(instance._processVariables).to.exist; + expect(instance._processVariables).to.have.a.property('addA').that.equals(3); + expect(instance._processVariables).to.have.a.property('addB').that.equals(7); + expect(instance._processVariables).to.have.a.property('addC').that.equals(11); + expect(instance._processVariables).to.have.a.property('scriptA').that.equals('done'); + expect(instance._processVariables).to.have.a.property('scriptB').that.equals('done'); + expect(instance._processVariables).to.have.a.property('scriptC').that.equals('done'); + done(); + }); + }); + + it('Process Tokens wont be Created for Link Events', function testFunction(done) { + let linkTokens = stateVerifier.fetchMatchingTokens(instance, 'Link'); + let tokenD = stateVerifier.fetchTokenByName(instance, 'D'); + let tokenC = stateVerifier.fetchTokenByName(instance, 'C'); + expect(linkTokens).to.exist.and.be.an('array').of.length(0); + expect(tokenD).to.not.exist; + expect(tokenC).to.not.exist; + done(); + }); + + it('Messages Can be Passed from one Node to other Node via Link Events', function testFunction(done) { + let scriptA2 = stateVerifier.fetchTokenByName(instance, 'ScriptA2'); + expect(scriptA2).to.exist.and.have.property('message'); + expect(scriptA2.message).to.have.a.property('textA').that.equals('HI TO LINK A'); + expect(scriptA2.message).to.have.a.property('resultA').that.equals(3); + let scriptB2 = stateVerifier.fetchTokenByName(instance, 'ScriptB2'); + expect(scriptB2).to.exist.and.have.property('message'); + expect(scriptB2.message).to.have.a.property('textB').that.equals('HI TO LINK B'); + expect(scriptB2.message).to.have.a.property('resultB').that.equals(7); + let scriptC2 = stateVerifier.fetchTokenByName(instance, 'ScriptC2'); + expect(scriptC2).to.exist.and.have.property('message'); + expect(scriptC2.message).to.have.a.property('textC').that.equals('HI TO LINK C'); + expect(scriptC2.message).to.have.a.property('resultC').that.equals(11); + done(); + }); + +}); \ No newline at end of file diff --git a/test/scripts/user-task-hook-tests.js b/test/scripts/user-task-hook-tests.js index 65529fb..39cb687 100644 --- a/test/scripts/user-task-hook-tests.js +++ b/test/scripts/user-task-hook-tests.js @@ -49,7 +49,8 @@ describe('User Task Hook Tests', function callback() { bootstrap.loadAndTrigger(workflowName, { processVariables: { testingHook: true, - sla: 5 + sla: 5, + modifyOptions: 'modifiedOptions' } }, function testFunction(err) { expect(err).to.not.exist; @@ -66,7 +67,7 @@ describe('User Task Hook Tests', function callback() { let date = new Date(Date.now()); date = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + processInstance._processVariables.sla)); expect(taskWithHook.dueDate).to.deep.equal(date); - + expect(bootstrap.defaultContext.modifyOptions).to.not.exist; taskWithHook.complete({}, bootstrap.defaultContext, function testFunction(err, data) { expect(err).to.exist; expect(err.message).to.equal('Comments must be provided'); @@ -76,14 +77,18 @@ describe('User Task Hook Tests', function callback() { expect(err).to.not.exist; expect(data).to.exist; expect(data.comments).to.equal('ok'); + expect(bootstrap.defaultContext.modifyOptions).to.exist; + expect(bootstrap.defaultContext.modifyOptions).to.equals('modifiedOptions'); done(); }); }); }); - it('Task with Hooks', function testFunction(done) { + it('Task with Invalid Hooks', function testFunction(done) { expect(taskInvalidHook).to.exist; expect(taskInvalidHook.dueDate).to.not.exist; + expect(bootstrap.defaultContext.modifyOptions).to.exist; + expect(bootstrap.defaultContext.modifyOptions).to.equals('modifiedOptions'); taskInvalidHook.complete({}, bootstrap.defaultContext, function testFunction(err, data) { expect(err).to.not.exist; done(); @@ -102,6 +107,8 @@ describe('User Task Hook Tests', function callback() { done(err); }); + expect(bootstrap.defaultContext.modifyOptions).to.exist; + expect(bootstrap.defaultContext.modifyOptions).to.equals('modifiedOptions'); taskNoHook.complete({}, bootstrap.defaultContext, function testFunction(err, data) { expect(err).to.exist; expect(err.message).to.equal('Default: Comments must be provided'); @@ -111,6 +118,7 @@ describe('User Task Hook Tests', function callback() { expect(err).to.not.exist; expect(data).to.exist; expect(data.comments).to.equal('ok'); + expect(bootstrap.defaultContext.modifyOptions).to.equals('defaultModifiedOptions'); }); }); }); diff --git a/test/scripts/user-task-tests.js b/test/scripts/user-task-tests.js index 7d4ffe8..c896325 100644 --- a/test/scripts/user-task-tests.js +++ b/test/scripts/user-task-tests.js @@ -968,6 +968,91 @@ describe('User Task Tests', function callback() { userTask = task; }); }); + + it('followUpDate can be updated through updateFollowUpDate', function testFunction(done) { + let newFollowUpDate = '22-11-2020'; + let usrContext = getContext('other', ['other'], 'other'); + expect(userTask).to.exist; + expect(userTask.followUpDate).to.not.equal(newFollowUpDate); + bootstrap.onComplete(workflowName, done); + userTask.updateFollowUpDate({}, usrContext, function testFunction(err, task) { + /* followUpDate is mandatory */ + expect(err).to.exist; + expect(err.code).to.equal('INVALID_DATA'); + expect(err.status).to.equal(400); + + userTask.updateFollowUpDate({ + followUpDate: newFollowUpDate + }, usrContext, function testFunction(err, task) { + expect(err).to.not.exist; + expect(task.status).to.not.equal(Status.COMPLETE); + expect(task).to.have.a.property('followUpDate').that.deep.equals(oeDateUtils.parseShorthand(newFollowUpDate)); + task.complete({}, getContext('other', ['other'], 'other'), function testFunction(err, task) { + expect(err).to.not.exist; + userTask = task; + }); + }); + }); + }); + + it('followUpDate can be set to null through updateFollowUpDate', function testFunction(done) { + let newFollowUpDate = null; + let usrContext = getContext('other', ['other'], 'other'); + expect(userTask).to.exist; + expect(userTask.followUpDate).to.not.equal(newFollowUpDate); + bootstrap.onComplete(workflowName, done); + userTask.updateFollowUpDate({ + followUpDate: newFollowUpDate + }, usrContext, function testFunction(err, task) { + expect(err).to.not.exist; + expect(task.status).to.not.equal(Status.COMPLETE); + expect(task).to.have.a.property('followUpDate').that.equals(null); + task.complete({}, getContext('other', ['other'], 'other'), function testFunction(err, task) { + expect(err).to.not.exist; + userTask = task; + }); + }); + + }); + + it('invalid followUpDate cannot be updated through updateFollowUpDate', function testFunction(done) { + let newFollowUpDate = 'invalid-date'; + let usrContext = getContext('other', ['other'], 'other'); + expect(userTask).to.exist; + expect(userTask.followUpDate).to.not.equal(newFollowUpDate); + bootstrap.onComplete(workflowName, done); + userTask.updateFollowUpDate({ + followUpDate: newFollowUpDate + }, usrContext, function testFunction(err, task) { + expect(err).to.exist; + expect(err.code).to.equal('INVALID_DATA'); + expect(err.statusCode).to.equal(422); + done(); + }); + }); + + it('followUpDate cannot be updated for completed tasks', function testFunction(done) { + let newFollowUpDate = '18-06-2020'; + expect(userTask).to.exist; + bootstrap.onComplete(workflowName, function testFunction(err, inst) { + expect(err).to.not.exist; + stateVerifier.isComplete(inst); + userTask.updateFollowUpDate({ + followUpDate: newFollowUpDate + }, getContext('other', ['other'], 'other'), function testFunction(err, task) { + expect(err).to.exist; + expect(err.code).to.equal('TASK_ALREADY_COMPLETED'); + expect(err.status).to.equal(409); + expect(task).to.not.exist; + done(); + }); + }); + userTask.complete({}, getContext('other', ['other'], 'other'), function testFunction(err, task) { + expect(err).to.not.exist; + expect(task.status).to.equal(Status.COMPLETE); + userTask = task; + }); + }); }); describe('Workflow Retry on user-task', function testFunction() { diff --git a/test/utils/addon-functions.js b/test/utils/addon-functions.js index a39fea4..ba35918 100644 --- a/test/utils/addon-functions.js +++ b/test/utils/addon-functions.js @@ -13,6 +13,12 @@ module.exports = { } return cb(err); }, + testPostCompletion: function postCompleteFunction(options, taskInstance, cb) { + if (this._processVariables.testingHook && this._processVariables.modifyOptions) { + options.modifyOptions = this._processVariables.modifyOptions; + } + return cb(options, taskInstance); + }, defaultTaskCreationHook: function defaultTaskCreationHook(options, taskDef, taskData, cb) { if (this._processVariables.testingHook) { @@ -28,5 +34,11 @@ module.exports = { err = new Error('Default: Comments must be provided'); } return cb(err); - } + }, + defaultTaskPostCompletionHook: function defaultTaskPostCompletionHook(options, taskInstance, cb) { + if (this._processVariables.testingHook) { + options.modifyOptions = 'defaultModifiedOptions'; + } + return cb(options, taskInstance); + }, };