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);
+ },
};