botium-core 1.13.19 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/botium-cjs.js +328 -395
  2. package/dist/botium-cjs.js.map +1 -1
  3. package/dist/botium-es.js +328 -393
  4. package/dist/botium-es.js.map +1 -1
  5. package/package.json +24 -29
  6. package/samples/extensions/asserterHooks/botium.json +1 -0
  7. package/samples/extensions/logichooks/botium.json +1 -0
  8. package/samples/extensions/logichooks/custom/MyAsserter.js +3 -3
  9. package/src/Capabilities.js +2 -1
  10. package/src/containers/PluginConnectorContainer.js +8 -0
  11. package/src/containers/plugins/SimpleRestContainer.js +17 -9
  12. package/src/containers/plugins/index.js +29 -41
  13. package/src/helpers/HookUtils.js +32 -68
  14. package/src/scripting/Convo.js +26 -9
  15. package/src/scripting/ScriptingMemory.js +0 -24
  16. package/src/scripting/logichook/LogicHookUtils.js +27 -47
  17. package/src/scripting/logichook/logichooks/ConditionalBusinessHoursLogicHook.js +1 -1
  18. package/src/scripting/logichook/logichooks/ConditionalCapabilityValueBasedLogicHook.js +1 -1
  19. package/src/scripting/logichook/logichooks/ConditionalJsonPathBasedLogicHook.js +1 -1
  20. package/src/scripting/logichook/logichooks/ConditionalTimeBasedLogicHook.js +1 -1
  21. package/test/compiler/precompilerscript.spec.js +24 -26
  22. package/test/connectors/pluginconnectorcontainer.spec.js +60 -0
  23. package/test/connectors/simplerest.spec.js +24 -27
  24. package/test/convo/fillAndApplyScriptingMemory.spec.js +1 -47
  25. package/test/hooks/customhooks.spec.js +3 -25
  26. package/test/logichooks/hookfromsrc.spec.js +13 -3
  27. package/test/plugins/plugins.spec.js +29 -2
  28. package/test/scripting/logichooks/convos/conditional_steps_multiple_condition_groups_no_assertion.convo.txt +6 -6
  29. package/test/scripting/logichooks/convos/conditional_steps_multiple_mandatory_condition_groups.convo.txt +20 -0
  30. package/test/scripting/logichooks/convos/conditional_steps_multiple_optional_condition_groups.convo.txt +20 -0
  31. package/test/scripting/logichooks/customConditionalStepLogicHook.spec.js +16 -0
  32. package/test/security/allowUnsafe.spec.js +20 -129
  33. package/LICENSES-3RDPARTY.txt +0 -6450
  34. package/test/scripting/asserters/convos/customembeddedasserterwithhugo.convo.txt +0 -7
  35. package/test/scripting/asserters/convos/customembeddedasserterwithouthugo.convo.txt +0 -7
  36. package/test/scripting/asserters/customEmbeddedAsserter.json +0 -14
  37. package/test/scripting/asserters/customEmbeddedAsserter.spec.js +0 -55
  38. package/test/scripting/logichooks/convos/custom_embedded.convo.txt +0 -8
  39. package/test/scripting/logichooks/customEmbedded.json +0 -14
  40. package/test/scripting/logichooks/customEmbedded.spec.js +0 -44
  41. package/test/security/convos/withscriptingmemoryfunction.convo.txt +0 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botium-core",
3
- "version": "1.13.19",
3
+ "version": "1.14.1",
4
4
  "description": "The Selenium for Chatbots",
5
5
  "main": "index.js",
6
6
  "module": "dist/botium-es.js",
@@ -18,7 +18,6 @@
18
18
  "link": "npm link botium-connector-dialogflow botium-connector-webdriverio botium-connector-directline3 botium-connector-watson botium-connector-alexa-smapi botium-connector-echo",
19
19
  "test": "cross-env NODE_PATH=\"./test/plugins/plugindir/fromfolder:./test/plugins/plugindir/fromfile:./test/security/resources\" mocha \"./test/**/*.spec.js\"",
20
20
  "coverage:report": "nyc report --reporter=lcov npm test",
21
- "license-checker": "license-checker > LICENSES-3RDPARTY.txt",
22
21
  "update-dependencies": "npm-check-updates --reject globby,rollup -u --timeout 120000"
23
22
  },
24
23
  "repository": {
@@ -32,14 +31,13 @@
32
31
  },
33
32
  "homepage": "https://www.botium.ai",
34
33
  "dependencies": {
35
- "@babel/runtime": "^7.21.5",
36
- "async": "^3.2.4",
34
+ "@babel/runtime": "^7.23.2",
35
+ "async": "^3.2.5",
37
36
  "body-parser": "^1.20.2",
38
37
  "boolean": "^3.2.0",
39
38
  "bottleneck": "^2.19.5",
40
- "csv-parse": "^5.3.10",
39
+ "csv-parse": "^5.5.2",
41
40
  "debug": "^4.3.4",
42
- "esprima": "^4.0.1",
43
41
  "express": "^4.18.2",
44
42
  "globby": "11.0.4",
45
43
  "ioredis": "^5.3.2",
@@ -47,52 +45,49 @@
47
45
  "is-json": "^2.0.1",
48
46
  "jsonpath": "^1.1.1",
49
47
  "lodash": "^4.17.21",
50
- "markdown-it": "^13.0.1",
48
+ "markdown-it": "^13.0.2",
51
49
  "mime-types": "^2.1.35",
52
50
  "mkdirp": "^3.0.1",
53
51
  "moment": "^2.29.4",
54
52
  "moment-timezone": "^0.5.43",
55
53
  "mustache": "^4.2.0",
56
54
  "promise-retry": "^2.0.1",
57
- "promise.allsettled": "^1.0.6",
55
+ "promise.allsettled": "^1.0.7",
58
56
  "randomatic": "^3.1.1",
59
57
  "request": "^2.88.2",
60
- "rimraf": "^5.0.0",
58
+ "rimraf": "^5.0.5",
61
59
  "sanitize-filename": "^1.6.3",
62
60
  "slugify": "^1.6.6",
63
- "socket.io": "^4.6.1",
64
- "socket.io-client": "^4.6.1",
61
+ "socket.io": "^4.7.2",
62
+ "socket.io-client": "^4.7.2",
65
63
  "socketio-auth": "^0.1.1",
66
64
  "swagger-jsdoc": "^6.2.8",
67
- "swagger-ui-express": "^4.6.3",
68
- "uuid": "^9.0.0",
69
- "vm2": "^3.9.17",
65
+ "swagger-ui-express": "^5.0.0",
66
+ "uuid": "^9.0.1",
70
67
  "word-error-rate": "0.0.7",
71
68
  "write-yaml": "^1.0.0",
72
69
  "xlsx": "^0.18.5",
73
70
  "xregexp": "^5.1.1",
74
- "yaml": "^2.2.2"
71
+ "yaml": "^2.3.4"
75
72
  },
76
73
  "devDependencies": {
77
- "@babel/core": "^7.21.8",
78
- "@babel/node": "^7.20.7",
79
- "@babel/plugin-transform-runtime": "^7.21.4",
80
- "@babel/preset-env": "^7.21.5",
81
- "chai": "^4.3.7",
74
+ "@babel/core": "^7.23.3",
75
+ "@babel/node": "^7.22.19",
76
+ "@babel/plugin-transform-runtime": "^7.23.3",
77
+ "@babel/preset-env": "^7.23.3",
78
+ "chai": "^4.3.10",
82
79
  "chai-as-promised": "^7.1.1",
83
80
  "cross-env": "^7.0.3",
84
- "eslint": "^8.40.0",
85
- "eslint-config-standard": "^17.0.0",
86
- "eslint-plugin-import": "^2.27.5",
87
- "eslint-plugin-mocha": "^10.1.0",
88
- "eslint-plugin-n": "^15.7.0",
81
+ "eslint": "^8.53.0",
82
+ "eslint-config-standard": "^17.1.0",
83
+ "eslint-plugin-import": "^2.29.0",
84
+ "eslint-plugin-mocha": "^10.2.0",
85
+ "eslint-plugin-n": "^16.3.1",
89
86
  "eslint-plugin-promise": "^6.1.1",
90
87
  "eslint-plugin-standard": "^4.1.0",
91
- "license-checker": "^25.0.1",
92
- "license-compatibility-checker": "^0.3.5",
93
88
  "mocha": "^10.2.0",
94
- "nock": "^13.3.1",
95
- "npm-check-updates": "^16.10.12",
89
+ "nock": "^13.3.8",
90
+ "npm-check-updates": "^16.14.6",
96
91
  "nyc": "^15.1.0",
97
92
  "rollup": "2.79.1",
98
93
  "rollup-plugin-babel": "^4.4.0",
@@ -7,6 +7,7 @@
7
7
  "WATSON_PASSWORD": "ZWDE5xo02sby",
8
8
  "WATSON_WORKSPACE_ID": "97513bc0-c581-4bec-ac9f-ea6a8ec308a9",
9
9
  "SCRIPTING_ENABLE_MEMORY": true,
10
+ "SAFEDIR": ".",
10
11
  "ASSERTERS": [
11
12
  {
12
13
  "ref": "DUMMY",
@@ -3,6 +3,7 @@
3
3
  "Capabilities": {
4
4
  "PROJECTNAME": "Botium Logichooks Sample",
5
5
  "CONTAINERMODE": "echo",
6
+ "SAFEDIR": ".",
6
7
  "ASSERTERS": [
7
8
  {
8
9
  "ref": "MYASSERTER",
@@ -6,17 +6,17 @@ module.exports = class MyAsserter {
6
6
  this.caps = caps
7
7
  }
8
8
 
9
- assertConvoBegin ({convo, container, args}) {
9
+ assertConvoBegin ({ convo, container, args }) {
10
10
  console.log(`MyAsserter assertConvoBegin: ${convo.header.name}`)
11
11
  return Promise.resolve()
12
12
  }
13
13
 
14
- assertConvoStep ({convo, convoStep, args, botMsg}) {
14
+ assertConvoStep ({ convo, convoStep, args, botMsg }) {
15
15
  console.log(`MyAsserter assertConvoStep, botMessage: ${utils.inspect(botMsg)} ...`)
16
16
  return Promise.resolve()
17
17
  }
18
18
 
19
- assertConvoEnd ({convo, container, transcript, args}) {
19
+ assertConvoEnd ({ convo, container, transcript, args }) {
20
20
  console.log(`MyAsserter assertConvoEnd ${convo.header.name}, transcript: ${utils.inspect(transcript)} ...`)
21
21
  return Promise.resolve()
22
22
  }
@@ -3,6 +3,7 @@ module.exports = {
3
3
  TESTSESSIONNAME: 'TESTSESSIONNAME',
4
4
  TESTCASENAME: 'TESTCASENAME',
5
5
  TEMPDIR: 'TEMPDIR',
6
+ SAFEDIR: 'SAFEDIR',
6
7
  CLEANUPTEMPDIR: 'CLEANUPTEMPDIR',
7
8
  WAITFORBOTTIMEOUT: 'WAITFORBOTTIMEOUT',
8
9
  CONTAINERMODE: 'CONTAINERMODE',
@@ -12,7 +13,7 @@ module.exports = {
12
13
  BOTIUMGRIDURL: 'BOTIUMGRIDURL',
13
14
  BOTIUMAPITOKEN: 'BOTIUMAPITOKEN',
14
15
  BOTIUMGRIDSLOT: 'BOTIUMGRIDSLOT',
15
- // Simple Reset Bot Settings
16
+ // Simple Rest Bot Settings
16
17
  SIMPLEREST_PING_URL: 'SIMPLEREST_PING_URL',
17
18
  SIMPLEREST_PING_VERB: 'SIMPLEREST_PING_VERB',
18
19
  SIMPLEREST_PING_BODY: 'SIMPLEREST_PING_BODY',
@@ -162,4 +162,12 @@ module.exports = class PluginConnectorContainer extends BaseContainer {
162
162
  return Promise.reject(new Error(`Clean - Botium plugin failed: ${util.inspect(err)}`))
163
163
  }
164
164
  }
165
+
166
+ GetMetaData () {
167
+ try {
168
+ return (this.pluginInstance.GetMetaData ? (this.pluginInstance.GetMetaData() || Promise.resolve()) : Promise.resolve())
169
+ } catch (err) {
170
+ return Promise.reject(new Error(`GetMetaData - Botium plugin failed: ${util.inspect(err)}`))
171
+ }
172
+ }
165
173
  }
@@ -336,24 +336,33 @@ module.exports = class SimpleRestContainer {
336
336
 
337
337
  const result = []
338
338
  if (isFromUser) {
339
- const _extractFrom = (root, jsonPaths) => {
340
- const flattened = []
339
+ const _extractFrom = (root, jsonPaths, acceptFn = null) => {
340
+ const result = []
341
341
  for (const jsonPath of jsonPaths) {
342
+ const jsonPathRes = []
342
343
  const rb = jp.query(root, jsonPath)
343
344
  if (_.isArray(rb)) {
344
- _.flattenDeep(rb).forEach(r => flattened.push(r))
345
- } else if (rb) {
346
- flattened.push(rb)
345
+ _.flattenDeep(rb).forEach(r => jsonPathRes.push(r))
346
+ } else {
347
+ jsonPathRes.push(rb)
348
+ }
349
+ if (acceptFn) {
350
+ result.push(...jsonPathRes.filter(r => acceptFn(root, jsonPath, r)))
351
+ } else {
352
+ result.push(...jsonPathRes)
347
353
  }
348
354
  }
349
- return flattened
355
+ return result
350
356
  }
351
357
 
352
358
  const jsonPathRoots = []
353
-
354
359
  const jsonPathsBody = getAllCapValues(Capabilities.SIMPLEREST_BODY_JSONPATH, this.caps)
355
360
  if (jsonPathsBody.length > 0) {
356
- jsonPathRoots.push(..._extractFrom(body, jsonPathsBody))
361
+ jsonPathRoots.push(..._extractFrom(body, jsonPathsBody, (root, jsonPath, r) => {
362
+ if (r && _.isObject(r)) return true
363
+ debug(`Ignoring result body from ${jsonPath} - not a querieable object (${util.inspect(r)})`)
364
+ return false
365
+ }))
357
366
  } else {
358
367
  jsonPathRoots.push(body)
359
368
  }
@@ -381,7 +390,6 @@ module.exports = class SimpleRestContainer {
381
390
  const jsonPathsButtonsPayload = getAllCapValues(`${capPrefix}_PAYLOAD_SUBJSONPATH`, this.caps)
382
391
 
383
392
  const retrievedButtons = []
384
-
385
393
  const responseButtons = _extractFrom(jsonPathButtonRoot, jsonPathsButtons)
386
394
  for (const responseButton of responseButtons) {
387
395
  const retrievedButton = {}
@@ -5,7 +5,6 @@ const debug = require('debug')('botium-connector-PluginConnectorContainer-helper
5
5
 
6
6
  const SimpleRestContainer = require('./SimpleRestContainer')
7
7
  const Capabilities = require('../../Capabilities')
8
- const { BotiumError } = require('../../scripting/BotiumError')
9
8
 
10
9
  const pluginResolver = (containermode) => {
11
10
  if (containermode === 'simplerest') {
@@ -59,23 +58,6 @@ const loadConnectorModule = (PluginClass, args) => {
59
58
 
60
59
  const tryLoadPlugin = (containermode, modulepath, args) => {
61
60
  const pluginLoaderSpec = modulepath || containermode
62
- const _checkUnsafe = (caps, mode, cause) => {
63
- if (!caps[Capabilities.SECURITY_ALLOW_UNSAFE]) {
64
- throw new BotiumError(
65
- `Security Error. Using unsafe connector mode "${mode}" is not allowed`,
66
- {
67
- type: 'security',
68
- subtype: 'allow unsafe',
69
- source: 'src/containers/plugins/index.js',
70
- cause: {
71
- SECURITY_ALLOW_UNSAFE: caps[Capabilities.SECURITY_ALLOW_UNSAFE],
72
- mode,
73
- ...cause
74
- }
75
- }
76
- )
77
- }
78
- }
79
61
 
80
62
  if (pluginResolver(pluginLoaderSpec)) {
81
63
  const pluginInstance = new (pluginResolver(pluginLoaderSpec))(args)
@@ -88,44 +70,50 @@ const tryLoadPlugin = (containermode, modulepath, args) => {
88
70
  return pluginInstance
89
71
  }
90
72
  const loadErr = []
73
+ const allowUnsafe = !!args.caps[Capabilities.SECURITY_ALLOW_UNSAFE]
91
74
 
92
75
  if (_.isString(pluginLoaderSpec)) {
93
- const tryLoadFile = path.resolve(process.cwd(), pluginLoaderSpec)
94
- if (fs.existsSync(tryLoadFile)) {
95
- _checkUnsafe(args.caps, 'Using work dir', { modulepath, containermode })
76
+ if (args.caps.SAFEDIR) {
77
+ const tryLoadFile = path.resolve(args.caps.SAFEDIR, pluginLoaderSpec)
78
+ if (tryLoadFile.startsWith(path.resolve(args.caps.SAFEDIR))) {
79
+ if (fs.existsSync(tryLoadFile)) {
80
+ try {
81
+ let plugin = require(tryLoadFile)
82
+ if (plugin.default) {
83
+ plugin = plugin.default
84
+ }
85
+ if (!plugin.PluginVersion || !plugin.PluginClass) {
86
+ loadErr.push(`Invalid Botium plugin loaded from ${tryLoadFile}, expected PluginVersion, PluginClass fields`)
87
+ } else {
88
+ const pluginInstance = loadConnectorModule(plugin.PluginClass, args)
89
+ debug(`Botium plugin loaded from ${tryLoadFile}`)
90
+ return pluginInstance
91
+ }
92
+ } catch (err) {
93
+ loadErr.push(`Loading Botium plugin from ${tryLoadFile} failed - ${err.message}`)
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ if (allowUnsafe) {
96
100
  try {
97
- let plugin = require(tryLoadFile)
101
+ let plugin = require(pluginLoaderSpec)
98
102
  if (plugin.default) {
99
103
  plugin = plugin.default
100
104
  }
101
105
  if (!plugin.PluginVersion || !plugin.PluginClass) {
102
- loadErr.push(`Invalid Botium plugin loaded from ${tryLoadFile}, expected PluginVersion, PluginClass fields`)
106
+ loadErr.push(`Invalid Botium plugin loaded from ${pluginLoaderSpec}, expected PluginVersion, PluginClass fields`)
103
107
  } else {
104
108
  const pluginInstance = loadConnectorModule(plugin.PluginClass, args)
105
- debug(`Botium plugin loaded from ${tryLoadFile}`)
109
+ debug(`Botium plugin loaded from ${pluginLoaderSpec}. Plugin version is ${getModuleVersionSafe(pluginLoaderSpec)}`)
106
110
  return pluginInstance
107
111
  }
108
112
  } catch (err) {
109
- loadErr.push(`Loading Botium plugin from ${tryLoadFile} failed - ${err.message}`)
113
+ loadErr.push(`Loading Botium plugin from ${pluginLoaderSpec} failed - ${err.message}`)
110
114
  }
111
115
  }
112
116
 
113
- try {
114
- let plugin = require(pluginLoaderSpec)
115
- if (plugin.default) {
116
- plugin = plugin.default
117
- }
118
- if (!plugin.PluginVersion || !plugin.PluginClass) {
119
- loadErr.push(`Invalid Botium plugin loaded from ${pluginLoaderSpec}, expected PluginVersion, PluginClass fields`)
120
- } else {
121
- const pluginInstance = loadConnectorModule(plugin.PluginClass, args)
122
- debug(`Botium plugin loaded from ${pluginLoaderSpec}. Plugin version is ${getModuleVersionSafe(pluginLoaderSpec)}`)
123
- return pluginInstance
124
- }
125
- } catch (err) {
126
- loadErr.push(`Loading Botium plugin from ${pluginLoaderSpec} failed - ${err.message}`)
127
- }
128
-
129
117
  const tryLoadPackage = `botium-connector-${pluginLoaderSpec}`
130
118
  try {
131
119
  let plugin = require(tryLoadPackage)
@@ -1,48 +1,28 @@
1
1
  const util = require('util')
2
2
  const path = require('path')
3
3
  const fs = require('fs')
4
- const { NodeVM } = require('vm2')
5
- const esprima = require('esprima')
6
4
  const _ = require('lodash')
7
5
  const debug = require('debug')('botium-core-HookUtils')
8
6
 
9
7
  const Capabilities = require('../Capabilities')
10
- const { BotiumError } = require('../scripting/BotiumError')
11
8
 
12
- const executeHook = async (caps, hook, args) => {
13
- return executeHookSync(caps, hook, args)
9
+ const executeHook = async (caps, hook, ...args) => {
10
+ return executeHookSync(caps, hook, ...args)
14
11
  }
15
12
 
16
- const executeHookSync = (caps, hook, args) => {
13
+ const executeHookSync = (caps, hook, ...args) => {
17
14
  if (!hook) {
18
15
  return
19
16
  }
20
17
 
21
18
  if (_.isFunction(hook)) {
22
19
  try {
23
- return hook(args)
20
+ return hook(...args)
24
21
  } catch (err) {
25
22
  throw new Error(`Calling Hook function failed: ${err.message}`)
26
23
  }
27
24
  }
28
25
 
29
- if (_.isString(hook)) {
30
- try {
31
- const vm = new NodeVM({
32
- eval: false,
33
- require: false,
34
- sandbox: args
35
- })
36
- const r = vm.run(hook)
37
- if (_.isFunction(r)) {
38
- return r(args)
39
- } else {
40
- return r
41
- }
42
- } catch (err) {
43
- throw new Error(`Calling Hook Javascript code failed: ${err.message}`)
44
- }
45
- }
46
26
  throw new Error(`Unknown hook ${typeof hook}`)
47
27
  }
48
28
 
@@ -58,56 +38,40 @@ const getHook = (caps, data) => {
58
38
  }
59
39
 
60
40
  if (_.isString(data)) {
61
- let resultWithRequire
62
- let tryLoadFile = path.resolve(process.cwd(), data)
63
- if (fs.existsSync(tryLoadFile)) {
64
- try {
65
- resultWithRequire = require(tryLoadFile)
66
- } catch (err) {
67
- }
68
- } else {
69
- tryLoadFile = data
70
- try {
71
- resultWithRequire = require(data)
72
- } catch (err) {
73
- }
74
- }
75
-
76
- if (resultWithRequire) {
77
- if (!allowUnsafe) {
78
- throw new BotiumError(
79
- 'Security Error. Using unsafe custom hook with require is not allowed',
80
- {
81
- type: 'security',
82
- subtype: 'allow unsafe',
83
- source: path.basename(__filename),
84
- cause: {
85
- SECURITY_ALLOW_UNSAFE: caps[Capabilities.SECURITY_ALLOW_UNSAFE],
86
- hookData: data
41
+ if (caps.SAFEDIR) {
42
+ const tryLoadFile = path.resolve(caps.SAFEDIR, data)
43
+ if (tryLoadFile.startsWith(path.resolve(caps.SAFEDIR))) {
44
+ if (fs.existsSync(tryLoadFile)) {
45
+ try {
46
+ const resultWithRequire = require(tryLoadFile)
47
+ if (_.isFunction(resultWithRequire)) {
48
+ debug(`found hook, type: safedir, in ${tryLoadFile}`)
49
+ return resultWithRequire
50
+ } else {
51
+ throw new Error(`Expected function from hook specification "${util.inspect(data)}", got: "${util.inspect(resultWithRequire)}"`)
87
52
  }
53
+ } catch (err) {
54
+ debug(`Failed loading hook, type: safedir, from ${tryLoadFile} failed: ${err.message || err}`)
88
55
  }
89
- )
90
- }
91
-
92
- if (_.isFunction(resultWithRequire)) {
93
- debug(`found hook, type: require, in ${tryLoadFile}`)
94
- return resultWithRequire
95
- } else {
96
- throw new Error(`Cant load hook ${tryLoadFile} because it is not a function`)
56
+ }
97
57
  }
98
58
  }
99
-
100
- try {
101
- esprima.parseScript(data)
102
- } catch (err) {
103
- throw new Error(`Cant load hook, syntax is not valid - ${util.inspect(err)}`)
59
+ if (allowUnsafe || data.startsWith('botium-')) {
60
+ const tryLoadFile = data
61
+ try {
62
+ const resultWithRequire = require(tryLoadFile)
63
+ if (_.isFunction(resultWithRequire)) {
64
+ debug(`found hook, type: require, in ${tryLoadFile}`)
65
+ return resultWithRequire
66
+ } else {
67
+ throw new Error(`Expected function from hook specification "${util.inspect(data)}", got: "${util.inspect(resultWithRequire)}"`)
68
+ }
69
+ } catch (err) {
70
+ debug(`Failed loading hook, type: require, from ${tryLoadFile} failed: ${err.message || err}`)
71
+ }
104
72
  }
105
-
106
- debug('Found hook, type: JavaScript as String')
107
- return data
108
73
  }
109
-
110
- throw new Error(`Not valid hook ${util.inspect(data)}`)
74
+ throw new Error(`Hook specification "${util.inspect(data)}" invalid: no loader available`)
111
75
  }
112
76
 
113
77
  module.exports = {
@@ -275,6 +275,8 @@ class Convo {
275
275
  let botMsg = null
276
276
  let waitForBotSays = true
277
277
  let skipTranscriptStep = false
278
+ let conditionalGroupId = null
279
+ let conditionMetInGroup = false
278
280
  for (let i = 0; i < this.conversation.length; i++) {
279
281
  const convoStep = this.conversation[i]
280
282
  const currentStepIndex = i
@@ -356,7 +358,6 @@ class Convo {
356
358
  throw failErr
357
359
  }
358
360
  } else if (convoStep.sender === 'bot') {
359
- const previousWaitForBotSays = waitForBotSays
360
361
  if (waitForBotSays) {
361
362
  botMsg = null
362
363
  } else {
@@ -406,22 +407,38 @@ class Convo {
406
407
  }
407
408
 
408
409
  if (convoStep.conditional) {
410
+ waitForBotSays = false
411
+ let endOfConditionalGroup = false
412
+ conditionalGroupId = convoStep.logicHooks.find(lh => lh.name.startsWith('CONDITIONAL_STEP')).args[1]
409
413
  const nextConvoStep = this.conversation[i + 1]
410
414
 
411
- if (!previousWaitForBotSays) {
412
- skipTranscriptStep = true
413
- }
414
- waitForBotSays = false
415
415
  if (!nextConvoStep || nextConvoStep.sender !== 'bot' || !nextConvoStep.logicHooks || !nextConvoStep.logicHooks.some(lh => lh.name.toUpperCase().startsWith('CONDITIONAL_STEP'))) {
416
- waitForBotSays = true
416
+ endOfConditionalGroup = true
417
417
  } else {
418
- const conditionalLogicHook = convoStep.logicHooks.find(lh => lh.name.startsWith('CONDITIONAL_STEP'))
419
418
  const nextConditionalLogicHook = nextConvoStep.logicHooks.find(lh => lh.name.startsWith('CONDITIONAL_STEP'))
420
- waitForBotSays = conditionalLogicHook.args[1] !== nextConditionalLogicHook.args[1]
419
+ endOfConditionalGroup = conditionalGroupId !== nextConditionalLogicHook.args[1]
421
420
  }
422
421
 
423
- if (convoStep.conditional.skip) {
422
+ if (convoStep.conditional.skip || conditionMetInGroup) {
423
+ skipTranscriptStep = true
424
+ if (endOfConditionalGroup && !conditionMetInGroup && !convoStep.optional) {
425
+ const failErr = new BotiumError(`${this.header.name}/${convoStep.stepTag}: Non of the conditions are met in ${conditionalGroupId ? `'${conditionalGroupId}' ` : ''}condition group`)
426
+ debug(failErr)
427
+ throw failErr
428
+ }
429
+ if (endOfConditionalGroup) {
430
+ waitForBotSays = !convoStep.optional
431
+ conditionalGroupId = undefined
432
+ conditionMetInGroup = false
433
+ }
424
434
  continue
435
+ } else {
436
+ conditionMetInGroup = true
437
+ if (endOfConditionalGroup) {
438
+ waitForBotSays = !convoStep.optional
439
+ conditionalGroupId = undefined
440
+ conditionMetInGroup = false
441
+ }
425
442
  }
426
443
  }
427
444
 
@@ -3,7 +3,6 @@ const debug = require('debug')('botium-core-ScriptingMemory')
3
3
  const randomize = require('randomatic')
4
4
  const { v1: uuidv1 } = require('uuid')
5
5
  const moment = require('moment')
6
- const { NodeVM } = require('vm2')
7
6
  const _ = require('lodash')
8
7
  const path = require('path')
9
8
  const jp = require('jsonpath')
@@ -181,29 +180,6 @@ const SCRIPTING_FUNCTIONS_RAW = {
181
180
  else return ''
182
181
  },
183
182
  numberOfArguments: 1
184
- },
185
-
186
- $func: {
187
- handler: (caps, code) => {
188
- if (code == null) {
189
- throw Error('func function used without args!')
190
- }
191
- try {
192
- const vm = new NodeVM({
193
- eval: false,
194
- require: false,
195
- env: caps[Capabilities.SECURITY_ALLOW_UNSAFE] ? process.env : {},
196
- sandbox: {
197
- caps,
198
- moment
199
- }
200
- })
201
- return vm.run(`module.exports = (${code})`)
202
- } catch (err) {
203
- throw Error(`func function execution failed - ${err}`)
204
- }
205
- },
206
- numberOfArguments: 1
207
183
  }
208
184
  }
209
185