botium-core 1.13.4 → 1.13.6

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 (70) hide show
  1. package/dist/botium-cjs.js +262 -88
  2. package/dist/botium-cjs.js.map +1 -1
  3. package/dist/botium-es.js +262 -88
  4. package/dist/botium-es.js.map +1 -1
  5. package/package.json +19 -19
  6. package/src/helpers/RetryHelper.js +3 -2
  7. package/src/scripting/CompilerXlsx.js +1 -0
  8. package/src/scripting/MatchFunctions.js +3 -1
  9. package/src/scripting/ScriptingMemory.js +41 -2
  10. package/src/scripting/ScriptingProvider.js +164 -56
  11. package/src/scripting/helper.js +2 -0
  12. package/src/scripting/logichook/asserter/BotRepliesConsumedAsserter.js +1 -1
  13. package/src/scripting/logichook/asserter/BotRepliesUnconsumedCountAsserter.js +1 -1
  14. package/src/scripting/logichook/asserter/ButtonsAsserter.js +1 -1
  15. package/src/scripting/logichook/asserter/ButtonsCountAsserter.js +1 -1
  16. package/src/scripting/logichook/asserter/ButtonsCountRecAsserter.js +1 -1
  17. package/src/scripting/logichook/asserter/CardsAsserter.js +1 -1
  18. package/src/scripting/logichook/asserter/CardsCountAsserter.js +1 -1
  19. package/src/scripting/logichook/asserter/CardsCountRecAsserter.js +1 -1
  20. package/src/scripting/logichook/asserter/EntitiesAsserter.js +1 -1
  21. package/src/scripting/logichook/asserter/EntityContentAsserter.js +2 -2
  22. package/src/scripting/logichook/asserter/EntityValuesAsserter.js +1 -1
  23. package/src/scripting/logichook/asserter/FormsAsserter.js +1 -1
  24. package/src/scripting/logichook/asserter/IntentAsserter.js +3 -3
  25. package/src/scripting/logichook/asserter/IntentConfidenceAsserter.js +4 -4
  26. package/src/scripting/logichook/asserter/IntentUniqueAsserter.js +2 -2
  27. package/src/scripting/logichook/asserter/JsonPathAsserter.js +5 -5
  28. package/src/scripting/logichook/asserter/JsonPathCountAsserter.js +2 -2
  29. package/src/scripting/logichook/asserter/MediaAsserter.js +1 -1
  30. package/src/scripting/logichook/asserter/MediaCountAsserter.js +1 -1
  31. package/src/scripting/logichook/asserter/MediaCountRecAsserter.js +1 -1
  32. package/src/scripting/logichook/asserter/PauseAsserter.js +1 -1
  33. package/src/scripting/logichook/asserter/ResponseLengthAsserter.js +3 -2
  34. package/src/scripting/logichook/asserter/TextContainsAllAsserter.js +2 -1
  35. package/src/scripting/logichook/asserter/TextContainsAllICAsserter.js +2 -1
  36. package/src/scripting/logichook/asserter/TextContainsAnyAsserter.js +1 -0
  37. package/src/scripting/logichook/asserter/TextContainsAnyICAsserter.js +2 -1
  38. package/src/scripting/logichook/asserter/TextEqualsAnyAsserter.js +2 -1
  39. package/src/scripting/logichook/asserter/TextEqualsAnyICAsserter.js +2 -1
  40. package/src/scripting/logichook/asserter/TextRegexpAllAsserter.js +2 -1
  41. package/src/scripting/logichook/asserter/TextRegexpAllICAsserter.js +2 -1
  42. package/src/scripting/logichook/asserter/TextRegexpAnyAsserter.js +1 -0
  43. package/src/scripting/logichook/asserter/TextRegexpAnyICAsserter.js +2 -1
  44. package/src/scripting/logichook/asserter/TextWildcardAllAsserter.js +2 -1
  45. package/src/scripting/logichook/asserter/TextWildcardAllICAsserter.js +2 -1
  46. package/src/scripting/logichook/asserter/TextWildcardAnyAsserter.js +1 -0
  47. package/src/scripting/logichook/asserter/TextWildcardAnyICAsserter.js +2 -1
  48. package/src/scripting/logichook/asserter/TextWildcardExactAllAsserter.js +2 -1
  49. package/src/scripting/logichook/asserter/TextWildcardExactAllICAsserter.js +2 -1
  50. package/src/scripting/logichook/asserter/TextWildcardExactAnyAsserter.js +1 -0
  51. package/src/scripting/logichook/asserter/TextWildcardExactAnyICAsserter.js +2 -1
  52. package/src/scripting/logichook/asserter/WerAsserter.js +10 -8
  53. package/src/scripting/logichook/userinput/MediaInput.js +2 -1
  54. package/test/compiler/compilercsv.spec.js +9 -2
  55. package/test/compiler/convos/csv/utterances_variable_row_len.csv +1 -1
  56. package/test/convo/fillAndApplyScriptingMemory.spec.js +64 -4
  57. package/test/convo/transcript.spec.js +15 -6
  58. package/test/scripting/asserters/convos/{wer_threshold_nok.yml → wer_threshold_nok_float.yml} +0 -0
  59. package/test/scripting/asserters/convos/wer_threshold_nok_percentage.yml +7 -0
  60. package/test/scripting/asserters/convos/{wer_threshold_ok.yml → wer_threshold_ok_float.yml} +0 -0
  61. package/test/scripting/asserters/convos/wer_threshold_ok_percentage.yml +7 -0
  62. package/test/scripting/asserters/entityContentAsserter.spec.js +2 -7
  63. package/test/scripting/asserters/intentAsserter.spec.js +1 -1
  64. package/test/scripting/asserters/intentConfidenceAsserter.spec.js +3 -7
  65. package/test/scripting/asserters/jsonpathAsserter.spec.js +1 -1
  66. package/test/scripting/asserters/werAsserter.spec.js +25 -5
  67. package/test/scripting/matching/matchingmode.spec.js +42 -2
  68. package/test/scripting/scriptingModificator.spec.js +1 -2
  69. package/test/scripting/scriptingProvider.spec.js +156 -4
  70. package/test/scripting/utteranceexpansion/associateByIndex.spec.js +15 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botium-core",
3
- "version": "1.13.4",
3
+ "version": "1.13.6",
4
4
  "description": "The Selenium for Chatbots",
5
5
  "main": "index.js",
6
6
  "module": "dist/botium-es.js",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "homepage": "https://www.botium.ai",
34
34
  "dependencies": {
35
- "@babel/runtime": "^7.18.6",
35
+ "@babel/runtime": "^7.19.0",
36
36
  "async": "^3.2.4",
37
37
  "body-parser": "^1.20.0",
38
38
  "boolean": "^3.2.0",
@@ -42,7 +42,7 @@
42
42
  "esprima": "^4.0.1",
43
43
  "express": "^4.18.1",
44
44
  "globby": "11.0.4",
45
- "ioredis": "^5.1.0",
45
+ "ioredis": "^5.2.3",
46
46
  "is-class": "^0.0.9",
47
47
  "is-json": "^2.0.1",
48
48
  "jsonpath": "^1.1.1",
@@ -59,13 +59,13 @@
59
59
  "rimraf": "^3.0.2",
60
60
  "sanitize-filename": "^1.6.3",
61
61
  "slugify": "^1.6.5",
62
- "socket.io": "^4.5.1",
63
- "socket.io-client": "^4.5.1",
62
+ "socket.io": "^4.5.2",
63
+ "socket.io-client": "^4.5.2",
64
64
  "socketio-auth": "^0.1.1",
65
- "swagger-jsdoc": "^6.2.1",
66
- "swagger-ui-express": "^4.4.0",
67
- "uuid": "^8.3.2",
68
- "vm2": "^3.9.10",
65
+ "swagger-jsdoc": "^6.2.5",
66
+ "swagger-ui-express": "^4.5.0",
67
+ "uuid": "^9.0.0",
68
+ "vm2": "^3.9.11",
69
69
  "word-error-rate": "0.0.7",
70
70
  "write-yaml": "^1.0.0",
71
71
  "xlsx": "^0.18.5",
@@ -73,27 +73,27 @@
73
73
  "yaml": "^2.1.1"
74
74
  },
75
75
  "devDependencies": {
76
- "@babel/core": "^7.18.6",
77
- "@babel/node": "^7.18.6",
78
- "@babel/plugin-transform-runtime": "^7.18.6",
79
- "@babel/preset-env": "^7.18.6",
76
+ "@babel/core": "^7.19.3",
77
+ "@babel/node": "^7.19.1",
78
+ "@babel/plugin-transform-runtime": "^7.19.1",
79
+ "@babel/preset-env": "^7.19.3",
80
80
  "chai": "^4.3.6",
81
81
  "chai-as-promised": "^7.1.1",
82
82
  "cross-env": "^7.0.3",
83
- "eslint": "^8.19.0",
83
+ "eslint": "^8.24.0",
84
84
  "eslint-config-standard": "^17.0.0",
85
85
  "eslint-plugin-import": "^2.26.0",
86
86
  "eslint-plugin-mocha": "^10.1.0",
87
- "eslint-plugin-n": "^15.2.4",
88
- "eslint-plugin-promise": "^6.0.0",
87
+ "eslint-plugin-n": "^15.3.0",
88
+ "eslint-plugin-promise": "^6.0.1",
89
89
  "eslint-plugin-standard": "^4.1.0",
90
90
  "license-checker": "^25.0.1",
91
91
  "license-compatibility-checker": "^0.3.5",
92
92
  "mocha": "^10.0.0",
93
- "nock": "^13.2.8",
94
- "npm-check-updates": "^15.2.6",
93
+ "nock": "^13.2.9",
94
+ "npm-check-updates": "^16.3.4",
95
95
  "nyc": "^15.1.0",
96
- "rollup": "^2.76.0",
96
+ "rollup": "^2.79.1",
97
97
  "rollup-plugin-babel": "^4.4.0",
98
98
  "rollup-plugin-commonjs": "^10.1.0",
99
99
  "rollup-plugin-json": "^4.0.0",
@@ -25,8 +25,9 @@ module.exports = class RetryHelper {
25
25
  factor: caps[`RETRY_${section.toUpperCase()}_FACTOR`] || (_.isNil(options.factor) ? 1 : options.factor),
26
26
  minTimeout: caps[`RETRY_${section.toUpperCase()}_MINTIMEOUT`] || (_.isNil(options.minTimeout) ? 1000 : options.minTimeout)
27
27
  }
28
-
29
- debug(`Retry for ${section} is ${this.retrySettings.retries > 0 ? 'enabled' : 'disabled'}. Settings: ${JSON.stringify(this.retrySettings)} Patterns: ${JSON.stringify(this.retryErrorPatterns.map(r => r.toString()))}`)
28
+ if (this.retrySettings.retries > 0) {
29
+ debug(`Retry for ${section} is enabled. Settings: ${JSON.stringify(this.retrySettings)} Patterns: ${JSON.stringify(this.retryErrorPatterns.map(r => r.toString()))}`)
30
+ }
30
31
  }
31
32
 
32
33
  shouldRetry (err) {
@@ -256,6 +256,7 @@ module.exports = class CompilerXlsx extends CompilerBase {
256
256
  if (!convo.header.name) {
257
257
  convo.header.name = `${convo.header.sheetname}-${this.colnames[convo.header.colindex]}${formatRowIndex(convo.header.rowindex)}`
258
258
  }
259
+ // it is not used anymore?
259
260
  convo.header.sort = convo.header.name
260
261
  scriptResults.push(convo)
261
262
  })
@@ -83,7 +83,9 @@ const equals = (ignoreCase) => (botresponse, utterance) => {
83
83
  const wer = () => (botresponse, utterance, args) => {
84
84
  botresponse = _normalize(botresponse || '')
85
85
  utterance = toString(utterance || '')
86
- return speechScorer.wordErrorRate(botresponse, utterance) <= args[0]
86
+
87
+ const threshold = ([',', '.'].find(p => `${args[0]}`.includes(p)) ? parseFloat(args[0]) : parseInt(args[0]) / 100)
88
+ return speechScorer.wordErrorRate(botresponse, utterance) <= threshold
87
89
  }
88
90
 
89
91
  const getMatchFunction = (matchingMode) => {
@@ -73,6 +73,32 @@ const SCRIPTING_FUNCTIONS_RAW = {
73
73
  return Date.now()
74
74
  },
75
75
 
76
+ $tomorrow: (pattern) => {
77
+ if (pattern) {
78
+ return moment().add(1, 'day').format(pattern)
79
+ }
80
+ return moment().add(1, 'day').toDate().toLocaleDateString()
81
+ },
82
+ $yesterday: (pattern) => {
83
+ if (pattern) {
84
+ return moment().subtract(1, 'day').format(pattern)
85
+ }
86
+ return moment().subtract(1, 'day').toDate().toLocaleDateString()
87
+ },
88
+
89
+ $date_add: (amount, unit, pattern) => {
90
+ if (pattern) {
91
+ return moment().add(amount, unit).format(pattern)
92
+ }
93
+ return moment().add(amount, unit).toDate().toLocaleDateString()
94
+ },
95
+ $date_subtract: (amount, unit, pattern) => {
96
+ if (pattern) {
97
+ return moment().subtract(amount, unit).format(pattern)
98
+ }
99
+ return moment().subtract(amount, unit).toDate().toLocaleDateString()
100
+ },
101
+
76
102
  $year: () => {
77
103
  return new Date().getFullYear()
78
104
  },
@@ -168,7 +194,8 @@ const SCRIPTING_FUNCTIONS_RAW = {
168
194
  require: false,
169
195
  env: caps[Capabilities.SECURITY_ALLOW_UNSAFE] ? process.env : {},
170
196
  sandbox: {
171
- caps
197
+ caps,
198
+ moment
172
199
  }
173
200
  })
174
201
  return vm.run(`module.exports = (${code})`)
@@ -246,7 +273,19 @@ const _apply = (scriptingMemory, str, caps, mockMsg) => {
246
273
  for (const match of matches) {
247
274
  if (match.indexOf('(') > 0) {
248
275
  const arg = match.substring(match.indexOf('(') + 1, match.lastIndexOf(')')).replace(/\\\)/g, ')')
249
- str = str.replace(match, SCRIPTING_FUNCTIONS[key].handler(caps, arg, mockMsg))
276
+ let args = [arg]
277
+ if (SCRIPTING_FUNCTIONS[key].numberOfArguments > 1) {
278
+ args = arg.split(',')
279
+ }
280
+ args = args.map(arg => {
281
+ arg = arg.trim()
282
+ if (arg.startsWith('"') && arg.endsWith('"')) {
283
+ return arg.substring(1, arg.length - 1)
284
+ } else {
285
+ return arg
286
+ }
287
+ })
288
+ str = str.replace(match, SCRIPTING_FUNCTIONS[key].handler(caps, ...args, mockMsg))
250
289
  } else {
251
290
  str = str.replace(match, SCRIPTING_FUNCTIONS[key].handler(caps))
252
291
  }
@@ -135,7 +135,7 @@ module.exports = class ScriptingProvider {
135
135
  }
136
136
  debug(`assertBotResponse ${stepTag} ${meMsg ? `(${meMsg}) ` : ''}BOT: ${botresponse} = ${tomatch} ...`)
137
137
  const found = _.find(tomatch, (utt) => this.matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]))
138
- const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'WerAsserter' : 'TextMatchAsserter'
138
+ const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
139
139
  if (_.isNil(found)) {
140
140
  let message = `${stepTag}: Bot response `
141
141
  message += meMsg ? `(on ${meMsg}) ` : ''
@@ -148,13 +148,18 @@ module.exports = class ScriptingProvider {
148
148
  {
149
149
  type: 'asserter',
150
150
  source: asserterType,
151
+ params: {
152
+ matchingMode: this.caps[Capabilities.SCRIPTING_MATCHING_MODE],
153
+ args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
154
+ },
151
155
  context: {
152
156
  stepTag
153
157
  },
154
158
  cause: {
155
159
  expected: tomatch,
156
160
  actual: botresponse,
157
- matchingMode: this.caps[Capabilities.SCRIPTING_MATCHING_MODE]
161
+ matchingMode: this.caps[Capabilities.SCRIPTING_MATCHING_MODE],
162
+ args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
158
163
  }
159
164
  }
160
165
  )
@@ -166,7 +171,7 @@ module.exports = class ScriptingProvider {
166
171
  }
167
172
  debug(`assertBotNotResponse ${stepTag} ${meMsg ? `(${meMsg}) ` : ''}BOT: ${botresponse} != ${nottomatch} ...`)
168
173
  const found = _.find(nottomatch, (utt) => this.matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]))
169
- const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'WerAsserter' : 'TextMatchAsserter'
174
+ const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
170
175
  if (!_.isNil(found)) {
171
176
  let message = `${stepTag}: Bot response `
172
177
  message += meMsg ? `(on ${meMsg}) ` : ''
@@ -179,6 +184,10 @@ module.exports = class ScriptingProvider {
179
184
  {
180
185
  type: 'asserter',
181
186
  source: asserterType,
187
+ params: {
188
+ matchingMode: this.caps[Capabilities.SCRIPTING_MATCHING_MODE],
189
+ args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
190
+ },
182
191
  context: {
183
192
  stepTag
184
193
  },
@@ -186,7 +195,8 @@ module.exports = class ScriptingProvider {
186
195
  not: true,
187
196
  expected: nottomatch,
188
197
  actual: botresponse,
189
- matchingMode: this.caps[Capabilities.SCRIPTING_MATCHING_MODE]
198
+ matchingMode: this.caps[Capabilities.SCRIPTING_MATCHING_MODE],
199
+ args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
190
200
  }
191
201
  }
192
202
  )
@@ -868,27 +878,68 @@ module.exports = class ScriptingProvider {
868
878
  this._sortConvos()
869
879
  }
870
880
 
871
- ExpandConvos () {
881
+ ExpandConvos (options = {}) {
882
+ options = Object.assign({
883
+ // use skip and keep, or justHeader
884
+ justHeader: false,
885
+ // drop unwanted convos
886
+ convoFilter: null
887
+ }, options)
888
+ const context = { count: 0 }
872
889
  const expandedConvos = []
873
890
  debug(`ExpandConvos - Using utterances expansion mode: ${this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE]}`)
874
891
  this.convos.forEach((convo) => {
875
892
  convo.expandPartialConvos()
876
- this._expandConvo(expandedConvos, convo)
893
+ for (const expanded of this._expandConvo(convo, options, context)) {
894
+ expanded.header.assertionCount = this.GetAssertionCount(expanded)
895
+ if (options.justHeader) {
896
+ const ConvoWithOnlyHeader = {
897
+ header: {
898
+ name: expanded.header.name,
899
+ assertionCount: expanded.header.assertionCount
900
+ }
901
+ }
902
+ expandedConvos.push(ConvoWithOnlyHeader)
903
+ } else {
904
+ expandedConvos.push(expanded)
905
+ }
906
+ }
877
907
  })
878
908
  this.convos = expandedConvos
879
- this._sortConvos()
909
+ if (!options.justHeader) {
910
+ this._sortConvos()
911
+ } else {
912
+ this._updateConvos()
913
+ }
914
+ }
915
+
916
+ ExpandConvosIterable (options = {}) {
917
+ options = Object.assign({
918
+ // drop unwanted convos
919
+ convoFilter: null
920
+ }, options)
921
+ debug(`ExpandConvos - Using utterances expansion mode: ${this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE]}`)
922
+ // creating a nested generator, calling the other.
923
+ // We hope this.convos does not changes while this iterator is used
924
+ const _convosIterable = function * (options) {
925
+ const context = { count: 0 }
926
+ for (const convo of this.convos) {
927
+ convo.expandPartialConvos()
928
+ yield * this._expandConvo(convo, options, context)
929
+ }
930
+ }.bind(this)
931
+
932
+ this.convosIterable = _convosIterable(options)
880
933
  }
881
934
 
882
935
  /**
883
- *
884
- * @param expandedConvos
936
+ * This is a generator function with yield
885
937
  * @param currentConvo
886
938
  * @param convoStepIndex
887
939
  * @param convoStepsStack list of ConvoSteps
888
- * @param context {width: }
889
940
  * @private
890
941
  */
891
- _expandConvo (expandedConvos, currentConvo, convoStepIndex = 0, convoStepsStack = [], context = {}) {
942
+ * _expandConvo (currentConvo, options, context, convoStepIndex = 0, convoStepsStack = []) {
892
943
  const utterancePostfix = (lineTag, uttOrUserInput) => {
893
944
  const naming = this.caps[Capabilities.SCRIPTING_UTTEXPANSION_NAMING_MODE] || Defaults.capabilities[Capabilities.SCRIPTING_UTTEXPANSION_NAMING_MODE]
894
945
  if (naming === 'justLineTag') {
@@ -908,7 +959,7 @@ module.exports = class ScriptingProvider {
908
959
  if (currentStep.sender === 'bot' || currentStep.sender === 'begin' || currentStep.sender === 'end') {
909
960
  const currentStepsStack = convoStepsStack.slice()
910
961
  currentStepsStack.push(_.cloneDeep(currentStep))
911
- this._expandConvo(expandedConvos, currentConvo, convoStepIndex + 1, currentStepsStack, context)
962
+ yield * this._expandConvo(currentConvo, options, context, convoStepIndex + 1, currentStepsStack)
912
963
  } else if (currentStep.sender === 'me') {
913
964
  let useUnexpanded = true
914
965
  if (currentStep.messageText) {
@@ -925,28 +976,32 @@ module.exports = class ScriptingProvider {
925
976
  }
926
977
  if (this.utterances[uttName]) {
927
978
  const allutterances = this.utterances[uttName].utterances
928
- const processSampleUtterances = (sampleutterances, myContext) => {
929
- sampleutterances.forEach((utt, index) => {
930
- processSampleUtterance(utt, sampleutterances.length, index, Object.assign({ indexExpansionModeIndex: index }, myContext || context))
931
- })
979
+ const processSampleUtterances = function * (sampleutterances, myContext) {
980
+ for (let index = 0; index < sampleutterances.length; index++) {
981
+ yield * processSampleUtterance(sampleutterances[index], sampleutterances.length, index, Object.assign({ indexExpansionModeIndex: index }, myContext || context))
982
+ }
932
983
  }
933
- const processSampleUtterance = (sampleutterance, length, index, myContext) => {
934
- const lineTag = `${index + 1}`.padStart(`${length}`.length, '0')
984
+ const processSampleUtterance = function * (sampleutterance, length, index, myContext) {
935
985
  const currentStepsStack = convoStepsStack.slice()
936
986
  if (uttArgs) {
937
987
  sampleutterance = util.format(sampleutterance, ...uttArgs)
938
988
  }
939
989
  currentStepsStack.push(Object.assign(_.cloneDeep(currentStep), { messageText: sampleutterance }))
940
990
  const currentConvoLabeled = _.cloneDeep(currentConvo)
941
- Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${uttName}-${utterancePostfix(lineTag, sampleutterance)}` })
991
+ if (length > 1) {
992
+ const lineTag = `${index + 1}`.padStart(`${length}`.length, '0')
993
+ Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${uttName}-${utterancePostfix(lineTag, sampleutterance)}` })
994
+ }
942
995
  if (!currentConvoLabeled.sourceTag) currentConvoLabeled.sourceTag = {}
943
996
  if (!currentConvoLabeled.sourceTag.origConvoName) currentConvoLabeled.sourceTag.origConvoName = currentConvo.header.name
944
- this._expandConvo(expandedConvos, currentConvoLabeled, convoStepIndex + 1, currentStepsStack, myContext || context)
945
- }
946
- if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'index') {
997
+ yield * this._expandConvo(currentConvoLabeled, options, myContext || context, convoStepIndex + 1, currentStepsStack)
998
+ }.bind(this)
999
+ if (allutterances.length === 1) {
1000
+ yield * processSampleUtterances([allutterances[0]], context)
1001
+ } else if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'index') {
947
1002
  if (_.isNil(context.indexExpansionModeWidth)) {
948
1003
  // executed for the first found utterance
949
- processSampleUtterances(allutterances, Object.assign({}, context, { indexExpansionModeWidth: allutterances.length }))
1004
+ yield * processSampleUtterances(allutterances, Object.assign({}, context, { indexExpansionModeWidth: allutterances.length }))
950
1005
  } else {
951
1006
  if (_.isNil(context.indexExpansionModeIndex)) {
952
1007
  throw new Error('indexExpansionModeIndex must be set!')
@@ -957,58 +1012,67 @@ module.exports = class ScriptingProvider {
957
1012
  debug(`While expanding convos by index found in utterance "${uttName}" less examples (${allutterances.length}) as expected (${context.indexExpansionModeWidth})`)
958
1013
  }
959
1014
  const myContext = Object.assign({}, context, { indexExpansionModeWidth: Math.max(allutterances.length, context.indexExpansionModeWidth) })
960
- processSampleUtterance(allutterances[localIndex], allutterances.length, localIndex, myContext)
1015
+ yield * processSampleUtterance(allutterances[localIndex], allutterances.length, localIndex, myContext)
961
1016
  if (allutterances.length > context.indexExpansionModeWidth && context.indexExpansionModeIndex + 1 === context.indexExpansionModeWidth) {
962
1017
  debug(`While expanding convos by index found in utterance "${uttName}" more examples (${allutterances.length}) as expected (${context.indexExpansionModeWidth})`)
963
1018
  for (let i = context.indexExpansionModeWidth; i < allutterances.length; i++) {
964
1019
  // if we found a utterance with more examples as any utterances before, we have to start new 'thread'
965
1020
  const myContext = Object.assign({}, context, { indexExpansionModeWidth: allutterances.length, indexExpansionModeIndex: i })
966
- processSampleUtterance(allutterances[i], allutterances.length, i, myContext)
1021
+ yield * processSampleUtterance(allutterances[i], allutterances.length, i, myContext)
967
1022
  }
968
1023
  }
969
1024
  }
970
1025
  } else {
971
1026
  if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'first') {
972
- processSampleUtterances([allutterances[0]])
1027
+ yield * processSampleUtterances([allutterances[0]], context)
973
1028
  } else if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'random') {
974
- processSampleUtterances(allutterances
1029
+ yield * processSampleUtterances(allutterances
975
1030
  .map(x => ({ x, r: Math.random() }))
976
1031
  .sort((a, b) => a.r - b.r)
977
1032
  .map(a => a.x)
978
- .slice(0, this.caps[Capabilities.SCRIPTING_UTTEXPANSION_RANDOM_COUNT]))
1033
+ .slice(0, this.caps[Capabilities.SCRIPTING_UTTEXPANSION_RANDOM_COUNT]), context)
979
1034
  } else {
980
- processSampleUtterances(allutterances)
1035
+ yield * processSampleUtterances(allutterances, context)
981
1036
  }
982
1037
  }
983
1038
  useUnexpanded = false
984
1039
  }
985
1040
  }
986
1041
  if (currentStep.userInputs && currentStep.userInputs.length > 0) {
987
- currentStep.userInputs.forEach((ui, uiIndex) => {
1042
+ for (let uiIndex = 0; uiIndex < currentStep.userInputs.length; uiIndex++) {
1043
+ const ui = currentStep.userInputs[uiIndex]
988
1044
  const userInput = this.userInputs[ui.name]
989
1045
  if (userInput && userInput.expandConvo) {
990
1046
  const expandedUserInputs = userInput.expandConvo({ convo: currentConvo, convoStep: currentStep, args: ui.args })
991
1047
  if (expandedUserInputs && expandedUserInputs.length > 0) {
992
1048
  // let sampleinputs = expandedUserInputs
993
- const processSampleInputs = (sampleinputs, myContext, uiIndex) => {
994
- sampleinputs.forEach((input, index) => {
995
- processSampleInput(input, sampleinputs.length, index, Object.assign({ indexExpansionModeIndex: index }, myContext || context), uiIndex)
996
- })
1049
+ const processSampleInputs = function * (sampleinputs, myContext, uiIndex) {
1050
+ for (let index = 0; index < sampleinputs.length; index++) {
1051
+ yield * processSampleInput(sampleinputs[index], sampleinputs.length, index, Object.assign({ indexExpansionModeIndex: index }, myContext || context), uiIndex)
1052
+ }
997
1053
  }
998
- const processSampleInput = (sampleinput, length, index, myContext, uiIndex) => {
999
- const lineTag = `${index + 1}`.padStart(`${length}`.length, '0')
1054
+ const processSampleInput = function * (sampleinput, length, index, myContext, uiIndex) {
1000
1055
  const currentStepsStack = convoStepsStack.slice()
1001
1056
  const currentStepMod = _.cloneDeep(currentStep)
1002
1057
  currentStepMod.userInputs[uiIndex] = sampleinput
1003
1058
 
1004
1059
  currentStepsStack.push(currentStepMod)
1005
1060
  const currentConvoLabeled = _.cloneDeep(currentConvo)
1006
- Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${ui.name}-${utterancePostfix(lineTag, (sampleinput.args && sampleinput.args.length) ? sampleinput.args.join(', ') : 'no-args')}` })
1007
- this._expandConvo(expandedConvos, currentConvoLabeled, convoStepIndex + 1, currentStepsStack, myContext || context)
1008
- }
1009
- if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'index') {
1061
+ if (length > 1) {
1062
+ if (sampleinput.convoPostfix) {
1063
+ Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${ui.name}-${sampleinput.convoPostfix}` })
1064
+ } else {
1065
+ const lineTag = `${index + 1}`.padStart(`${length}`.length, '0')
1066
+ Object.assign(currentConvoLabeled.header, { name: `${currentConvo.header.name}/${ui.name}-${utterancePostfix(lineTag, (sampleinput.args && sampleinput.args.length) ? sampleinput.args.join(', ') : 'no-args')}` })
1067
+ }
1068
+ }
1069
+ yield * this._expandConvo(currentConvoLabeled, options, myContext || context, convoStepIndex + 1, currentStepsStack)
1070
+ }.bind(this)
1071
+ if (expandedUserInputs.length === 1) {
1072
+ yield * processSampleInputs([expandedUserInputs[0]], context, uiIndex)
1073
+ } else if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'index') {
1010
1074
  if (_.isNil(context.indexExpansionModeWidth)) {
1011
- processSampleInputs(expandedUserInputs, Object.assign({}, context, { indexExpansionModeWidth: expandedUserInputs.length }), uiIndex)
1075
+ yield * processSampleInputs(expandedUserInputs, Object.assign({}, context, { indexExpansionModeWidth: expandedUserInputs.length }), uiIndex)
1012
1076
  } else {
1013
1077
  if (_.isNil(context.indexExpansionModeIndex)) {
1014
1078
  throw new Error('indexExpansionModeIndex must be set!')
@@ -1019,20 +1083,20 @@ module.exports = class ScriptingProvider {
1019
1083
  debug(`While expanding convos by index found user input "${ui.name}, ${ui.args}" less examples (${expandedUserInputs.length}) as expected (${context.indexExpansionModeWidth})`)
1020
1084
  }
1021
1085
  const myContext = Object.assign({}, context, { indexExpansionModeWidth: Math.max(expandedUserInputs.length, context.indexExpansionModeWidth) })
1022
- processSampleInput(expandedUserInputs[localIndex], expandedUserInputs.length, localIndex, myContext, uiIndex)
1086
+ yield * processSampleInput(expandedUserInputs[localIndex], expandedUserInputs.length, localIndex, myContext, uiIndex)
1023
1087
  if (expandedUserInputs.length > context.indexExpansionModeWidth && context.indexExpansionModeIndex + 1 === context.indexExpansionModeWidth) {
1024
1088
  debug(`While expanding convos by index found user input "${ui.name}, ${ui.args}" more examples (${expandedUserInputs.length}) as expected (${context.indexExpansionModeWidth})`)
1025
1089
  for (let i = context.indexExpansionModeWidth; i < expandedUserInputs.length; i++) {
1026
1090
  const myContext = Object.assign({}, context, { indexExpansionModeWidth: expandedUserInputs.length, indexExpansionModeIndex: i })
1027
- processSampleInput(expandedUserInputs[i], expandedUserInputs.length, i, myContext, uiIndex)
1091
+ yield * processSampleInput(expandedUserInputs[i], expandedUserInputs.length, i, myContext, uiIndex)
1028
1092
  }
1029
1093
  }
1030
1094
  }
1031
1095
  } else {
1032
1096
  if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'first') {
1033
- processSampleInputs([expandedUserInputs[0]], context, uiIndex)
1097
+ yield * processSampleInputs([expandedUserInputs[0]], context, uiIndex)
1034
1098
  } else if (this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE] === 'random') {
1035
- processSampleInputs(expandedUserInputs
1099
+ yield * processSampleInputs(expandedUserInputs
1036
1100
  .map(x => ({
1037
1101
  x,
1038
1102
  r: Math.random()
@@ -1041,35 +1105,49 @@ module.exports = class ScriptingProvider {
1041
1105
  .map(a => a.x)
1042
1106
  .slice(0, this.caps[Capabilities.SCRIPTING_UTTEXPANSION_RANDOM_COUNT]), context, uiIndex)
1043
1107
  } else {
1044
- processSampleInputs(expandedUserInputs, context, uiIndex)
1108
+ yield * processSampleInputs(expandedUserInputs, context, uiIndex)
1045
1109
  }
1046
1110
  }
1047
1111
  useUnexpanded = false
1048
1112
  }
1049
1113
  }
1050
- })
1114
+ }
1051
1115
  }
1052
1116
  if (useUnexpanded) {
1053
1117
  const currentStepsStack = convoStepsStack.slice()
1054
1118
  currentStepsStack.push(_.cloneDeep(currentStep))
1055
- this._expandConvo(expandedConvos, currentConvo, convoStepIndex + 1, currentStepsStack, context)
1119
+ yield * this._expandConvo(currentConvo, options, context, convoStepIndex + 1, currentStepsStack)
1056
1120
  }
1057
1121
  }
1058
1122
  } else {
1059
- expandedConvos.push(Object.assign(_.cloneDeep(currentConvo), { conversation: _.cloneDeep(convoStepsStack) }))
1123
+ const expanded = Object.assign(_.cloneDeep(currentConvo), { conversation: _.cloneDeep(convoStepsStack) })
1124
+ if (!options.convoFilter || options.convoFilter(expanded)) {
1125
+ context.count++
1126
+ const logPerEntry = context.count < 10 ? 1 : context.count < 100 ? 10 : context.count < 1000 ? 100 : context.count < 10000 ? 1000 : 10000
1127
+ if (context.count % logPerEntry === 0) {
1128
+ debug(`Convo #${context.count} expanded (${expanded.header.name})`)
1129
+ }
1130
+ yield expanded
1131
+ }
1060
1132
  }
1061
1133
  }
1062
1134
 
1063
1135
  _sortConvos () {
1064
1136
  this.convos = _.sortBy(this.convos, [(convo) => convo.header.sort || convo.header.name])
1137
+ this._updateConvos()
1138
+ }
1139
+
1140
+ _updateConvos () {
1065
1141
  let i = 0
1066
1142
  this.convos.forEach((convo) => {
1067
- convo.header.order = ++i
1068
- if (!convo.header.projectname) {
1069
- convo.header.projectname = this.caps[Capabilities.PROJECTNAME]
1070
- }
1071
- if (!convo.header.testsessionname) {
1072
- convo.header.testsessionname = this.caps[Capabilities.TESTSESSIONNAME]
1143
+ if (convo) {
1144
+ convo.header.order = ++i
1145
+ if (!convo.header.projectname) {
1146
+ convo.header.projectname = this.caps[Capabilities.PROJECTNAME]
1147
+ }
1148
+ if (!convo.header.testsessionname) {
1149
+ convo.header.testsessionname = this.caps[Capabilities.TESTSESSIONNAME]
1150
+ }
1073
1151
  }
1074
1152
  })
1075
1153
  }
@@ -1080,7 +1158,11 @@ module.exports = class ScriptingProvider {
1080
1158
  } else if (convos) {
1081
1159
  this.convos.push(convos)
1082
1160
  }
1083
- this._sortConvos()
1161
+ if (this.convos.filter(c => _.isNil(c))) {
1162
+ this._updateConvos()
1163
+ } else {
1164
+ this._sortConvos()
1165
+ }
1084
1166
  }
1085
1167
 
1086
1168
  AddUtterances (utterances) {
@@ -1334,4 +1416,30 @@ module.exports = class ScriptingProvider {
1334
1416
  ...lines,
1335
1417
  '}'].join('\r\n')
1336
1418
  }
1419
+
1420
+ GetAssertionCount (convo) {
1421
+ if (!convo) {
1422
+ return 0
1423
+ }
1424
+ let counter = 0
1425
+ for (const step of convo.conversation) {
1426
+ if (step.sender === 'bot') {
1427
+ let stepCounter = step.asserters ? step.asserters.length : 0
1428
+ if (step.messageText) {
1429
+ stepCounter++
1430
+ }
1431
+ stepCounter = stepCounter === 0 ? 1 : stepCounter
1432
+ counter += stepCounter
1433
+ }
1434
+ }
1435
+
1436
+ if (convo.convoBegin && convo.convoBegin.asserters) {
1437
+ counter += convo.convoBegin.asserters.length
1438
+ }
1439
+
1440
+ if (convo.convoEnd && convo.convoEnd.asserters) {
1441
+ counter += convo.convoEnd.asserters.length
1442
+ }
1443
+ return counter === 0 ? 1 : counter
1444
+ }
1337
1445
  }
@@ -437,6 +437,8 @@ const convoStepToLines = (step) => {
437
437
  lines.push('MEDIA ' + step.media[0].mediaUri)
438
438
  } else if (step.messageText) {
439
439
  lines.push(step.messageText)
440
+ } else if (step.sourceData) {
441
+ lines.push(JSON.stringify(step.sourceData, null, 2))
440
442
  }
441
443
  step.userInputs && step.userInputs.forEach((userInput) => {
442
444
  lines.push(userInput.name + _formatAppendArgs(userInput.args))
@@ -3,7 +3,7 @@ const BaseCountAsserter = require('./BaseCountAsserter')
3
3
  module.exports = class BotRepliesConsumedAsserter extends BaseCountAsserter {
4
4
  constructor (context, caps = {}) {
5
5
  super(context, caps, 'BotReplies')
6
- this.name = 'BotRepliesConsumedAsserter'
6
+ this.name = 'Bot Replies Consumed Asserter'
7
7
  }
8
8
 
9
9
  async _getCount (argv) { return argv.container._QueueLength() }
@@ -3,7 +3,7 @@ const BaseCountAsserter = require('./BaseCountAsserter')
3
3
  module.exports = class BotRepliesUnconsumedCountAsserter extends BaseCountAsserter {
4
4
  constructor (context, caps = {}) {
5
5
  super(context, caps, 'unconsumed bot replies')
6
- this.name = 'BotRepliesUnconsumedCountAsserter'
6
+ this.name = 'Bot Replies Unconsumed Count Asserter'
7
7
  }
8
8
 
9
9
  async _getCount (argv) { return argv.container._QueueLength() }
@@ -7,7 +7,7 @@ module.exports = class ButtonsAsserter {
7
7
  constructor (context, caps = {}) {
8
8
  this.context = context
9
9
  this.caps = caps
10
- this.name = 'ButtonsAsserter'
10
+ this.name = 'Buttons Asserter'
11
11
  }
12
12
 
13
13
  _evalButtons (args, botMsg) {
@@ -8,7 +8,7 @@ const _buttonsCount = ({ botMsg }) => {
8
8
  module.exports = class ButtonsCountAsserter extends BaseCountAsserter {
9
9
  constructor (context, caps = {}) {
10
10
  super(context, caps, 'Buttons')
11
- this.name = 'ButtonsCountAsserter'
11
+ this.name = 'Buttons Count Asserter'
12
12
  }
13
13
 
14
14
  async _getCount (argv) { return _buttonsCount(argv) }
@@ -8,7 +8,7 @@ const _buttonsCount = ({ botMsg }) => {
8
8
  module.exports = class ButtonsCountRecAsserter extends BaseCountAsserter {
9
9
  constructor (context, caps = {}) {
10
10
  super(context, caps, 'Buttons')
11
- this.name = 'ButtonsCountRecAsserter'
11
+ this.name = 'Buttons Count (recursive) Asserter'
12
12
  }
13
13
 
14
14
  async _getCount (argv) { return _buttonsCount(argv) }
@@ -7,7 +7,7 @@ module.exports = class CardsAsserter {
7
7
  constructor (context, caps = {}) {
8
8
  this.context = context
9
9
  this.caps = caps
10
- this.name = 'CardsAsserter'
10
+ this.name = 'Cards Asserter'
11
11
  }
12
12
 
13
13
  _evalCards (args, botMsg) {