botium-core 1.11.14 → 1.12.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 (52) hide show
  1. package/dist/botium-cjs.js +853 -579
  2. package/dist/botium-cjs.js.map +1 -1
  3. package/dist/botium-es.js +471 -198
  4. package/dist/botium-es.js.map +1 -1
  5. package/index.js +1 -0
  6. package/package.json +29 -29
  7. package/samples/connectors/custom/botium-connector-myapi.js +3 -3
  8. package/samples/extensions/asserterHooks/DummyAsserter.js +3 -3
  9. package/src/Capabilities.js +4 -1
  10. package/src/Defaults.js +1 -0
  11. package/src/Enums.js +6 -0
  12. package/src/containers/plugins/SimpleRestContainer.js +32 -1
  13. package/src/scripting/BotiumError.js +21 -0
  14. package/src/scripting/CompilerCsv.js +1 -1
  15. package/src/scripting/CompilerObjectBase.js +4 -14
  16. package/src/scripting/CompilerTxt.js +4 -15
  17. package/src/scripting/CompilerXlsx.js +81 -25
  18. package/src/scripting/Convo.js +16 -4
  19. package/src/scripting/MatchFunctions.js +21 -0
  20. package/src/scripting/ScriptingProvider.js +55 -40
  21. package/src/scripting/helper.js +57 -4
  22. package/src/scripting/logichook/LogicHookConsts.js +4 -0
  23. package/src/scripting/logichook/LogicHookUtils.js +2 -0
  24. package/src/scripting/logichook/asserter/JsonPathAsserter.js +1 -1
  25. package/src/scripting/logichook/asserter/TextWildcardExactAllAsserter.js +8 -0
  26. package/src/scripting/logichook/asserter/TextWildcardExactAllICAsserter.js +8 -0
  27. package/src/scripting/logichook/asserter/TextWildcardExactAnyAsserter.js +8 -0
  28. package/src/scripting/logichook/asserter/TextWildcardExactAnyICAsserter.js +8 -0
  29. package/src/scripting/logichook/logichooks/ClearQueueLogicHook.js +1 -1
  30. package/src/scripting/logichook/userinput/MediaInput.js +14 -2
  31. package/test/connectors/convos/hello.convo.txt +6 -0
  32. package/test/connectors/simplerest.spec.js +42 -2
  33. package/test/convo/convos/continuefailing.convo.txt +19 -0
  34. package/test/convo/transcript.spec.js +34 -0
  35. package/test/scripting/asserters/convos/text_wildcardexact_all_nok.yml +7 -0
  36. package/test/scripting/asserters/convos/text_wildcardexact_all_ok.yml +7 -0
  37. package/test/scripting/asserters/convos/text_wildcardexact_any_nok.yml +7 -0
  38. package/test/scripting/asserters/convos/text_wildcardexact_any_ok.yml +7 -0
  39. package/test/scripting/asserters/textWildcardExactAllAsserter.spec.js +51 -0
  40. package/test/scripting/asserters/textWildcardExactAnyAsserter.spec.js +51 -0
  41. package/test/scripting/matching/matchingmode.spec.js +43 -0
  42. package/test/scripting/scriptingProvider.spec.js +4 -4
  43. package/test/scripting/scriptingmemory/convosMultiMemorySameCols/buy.convo.txt +6 -0
  44. package/test/scripting/scriptingmemory/convosMultiMemorySameCols/products1.scriptingmemory.txt +2 -0
  45. package/test/scripting/scriptingmemory/convosMultiMemorySameCols/products2.scriptingmemory.txt +2 -0
  46. package/test/scripting/scriptingmemory/convosSimpleCols/buy.convo.txt +8 -0
  47. package/test/scripting/scriptingmemory/convosSimpleCols/product.scriptingmemory.txt +3 -0
  48. package/test/scripting/scriptingmemory/convosTwoTablesCols/buy.convo.txt +6 -0
  49. package/test/scripting/scriptingmemory/convosTwoTablesCols/customer.xlsx +0 -0
  50. package/test/scripting/scriptingmemory/convosTwoTablesCols/product.xlsx +0 -0
  51. package/test/scripting/scriptingmemory/fillScriptingMemoryFromFile.spec.js +45 -0
  52. package/test/scripting/userinputs/mediaInputConvos.spec.js +53 -2
@@ -464,11 +464,33 @@ module.exports = class ScriptingProvider {
464
464
  return { convos: dirConvos, utterances: dirUtterances, pconvos: dirPartialConvos, scriptingMemories: dirScriptingMemories }
465
465
  }
466
466
 
467
+ ReadScriptFromBuffer (scriptBuffer, scriptingFormat, scriptingTypes = null) {
468
+ if (_.isString(scriptingTypes)) scriptingTypes = [scriptingTypes]
469
+ if (_.isArray(scriptingTypes) && scriptingTypes.length === 0) scriptingTypes = null
470
+
471
+ const result = {
472
+ convos: [],
473
+ utterances: [],
474
+ pconvos: [],
475
+ scriptingMemories: []
476
+ }
477
+ if (!scriptingTypes || scriptingTypes.includes(Constants.SCRIPTING_TYPE_UTTERANCES)) {
478
+ result.utterances = this.Compile(scriptBuffer, scriptingFormat, Constants.SCRIPTING_TYPE_UTTERANCES)
479
+ }
480
+ if (!scriptingTypes || scriptingTypes.includes(Constants.SCRIPTING_TYPE_PCONVO)) {
481
+ result.pconvos = this.Compile(scriptBuffer, scriptingFormat, Constants.SCRIPTING_TYPE_PCONVO)
482
+ }
483
+ if (!scriptingTypes || scriptingTypes.includes(Constants.SCRIPTING_TYPE_CONVO)) {
484
+ result.convos = this.Compile(scriptBuffer, scriptingFormat, Constants.SCRIPTING_TYPE_CONVO)
485
+ }
486
+ if (!scriptingTypes || scriptingTypes.includes(Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY)) {
487
+ result.scriptingMemories = this.Compile(scriptBuffer, scriptingFormat, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY)
488
+ }
489
+ return result
490
+ }
491
+
467
492
  ReadScript (convoDir, filename) {
468
- let fileConvos = []
469
- let fileUtterances = []
470
- let filePartialConvos = []
471
- let fileScriptingMemories = []
493
+ let result = {}
472
494
 
473
495
  try {
474
496
  let scriptBuffer = fs.readFileSync(path.resolve(convoDir, filename))
@@ -487,36 +509,25 @@ module.exports = class ScriptingProvider {
487
509
  }
488
510
 
489
511
  if (filename.endsWith('.xlsx') || filename.endsWith('.xlsm')) {
490
- fileUtterances = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_XSLX, Constants.SCRIPTING_TYPE_UTTERANCES)
491
- filePartialConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_XSLX, Constants.SCRIPTING_TYPE_PCONVO)
492
- fileConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_XSLX, Constants.SCRIPTING_TYPE_CONVO)
493
- fileScriptingMemories = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_XSLX, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY)
512
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_XSLX, [Constants.SCRIPTING_TYPE_UTTERANCES, Constants.SCRIPTING_TYPE_PCONVO, Constants.SCRIPTING_TYPE_CONVO, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY])
494
513
  } else if (filename.endsWith('.convo.txt')) {
495
- fileConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_CONVO)
514
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_CONVO)
496
515
  } else if (filename.endsWith('.pconvo.txt')) {
497
- filePartialConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_PCONVO)
516
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_PCONVO)
498
517
  } else if (filename.endsWith('.utterances.txt')) {
499
- fileUtterances = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_UTTERANCES)
518
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_UTTERANCES)
500
519
  } else if (filename.endsWith('.scriptingmemory.txt')) {
501
- fileScriptingMemories = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY)
520
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY)
502
521
  } else if (filename.endsWith('.convo.csv')) {
503
- fileConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_CSV, Constants.SCRIPTING_TYPE_CONVO)
522
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_CSV, Constants.SCRIPTING_TYPE_CONVO)
504
523
  } else if (filename.endsWith('.pconvo.csv')) {
505
- filePartialConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_CSV, Constants.SCRIPTING_TYPE_PCONVO)
524
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_CSV, Constants.SCRIPTING_TYPE_PCONVO)
506
525
  } else if (filename.endsWith('.yaml') || filename.endsWith('.yml')) {
507
- fileUtterances = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_YAML, Constants.SCRIPTING_TYPE_UTTERANCES)
508
- filePartialConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_YAML, Constants.SCRIPTING_TYPE_PCONVO)
509
- fileConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_YAML, Constants.SCRIPTING_TYPE_CONVO)
510
- fileScriptingMemories = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_YAML, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY)
526
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_YAML, [Constants.SCRIPTING_TYPE_UTTERANCES, Constants.SCRIPTING_TYPE_PCONVO, Constants.SCRIPTING_TYPE_CONVO, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY])
511
527
  } else if (filename.endsWith('.json')) {
512
- fileUtterances = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_JSON, Constants.SCRIPTING_TYPE_UTTERANCES)
513
- filePartialConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_JSON, Constants.SCRIPTING_TYPE_PCONVO)
514
- fileConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_JSON, Constants.SCRIPTING_TYPE_CONVO)
515
- fileScriptingMemories = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_JSON, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY)
528
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_JSON, [Constants.SCRIPTING_TYPE_UTTERANCES, Constants.SCRIPTING_TYPE_PCONVO, Constants.SCRIPTING_TYPE_CONVO, Constants.SCRIPTING_TYPE_SCRIPTING_MEMORY])
516
529
  } else if (filename.endsWith('.markdown') || filename.endsWith('.md')) {
517
- fileUtterances = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_MARKDOWN, Constants.SCRIPTING_TYPE_UTTERANCES)
518
- fileConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_MARKDOWN, Constants.SCRIPTING_TYPE_CONVO)
519
- filePartialConvos = this.Compile(scriptBuffer, Constants.SCRIPTING_FORMAT_MARKDOWN, Constants.SCRIPTING_TYPE_PCONVO)
530
+ result = this.ReadScriptFromBuffer(scriptBuffer, Constants.SCRIPTING_FORMAT_MARKDOWN, [Constants.SCRIPTING_TYPE_UTTERANCES, Constants.SCRIPTING_TYPE_PCONVO, Constants.SCRIPTING_TYPE_CONVO])
520
531
  } else {
521
532
  debug(`ReadScript - dropped file: ${filename}, filename not supported`)
522
533
  }
@@ -526,42 +537,40 @@ module.exports = class ScriptingProvider {
526
537
  }
527
538
 
528
539
  // Compilers saved the convos, and we alter here the saved version too
529
- if (fileConvos) {
530
- fileConvos.forEach((fileConvo) => {
540
+ if (result.convos && result.convos.length > 0) {
541
+ result.convos.forEach((fileConvo) => {
531
542
  fileConvo.sourceTag = { convoDir, filename }
532
543
  if (!fileConvo.header.name) {
533
544
  fileConvo.header.name = filename
534
545
  }
535
546
  })
536
547
  const isSkip = (c) => c.header.name && skipPattern.test(c.header.name.toLowerCase())
537
- fileConvos.filter(c => isSkip(c)).forEach(c => debug(`ReadScript - skipping convo '${c.header.name}'`))
538
- fileConvos = fileConvos.filter(c => !isSkip(c))
548
+ result.convos.filter(c => isSkip(c)).forEach(c => debug(`ReadScript - skipping convo '${c.header.name}'`))
549
+ result.convos = result.convos.filter(c => !isSkip(c))
539
550
  }
540
- if (filePartialConvos) {
541
- filePartialConvos.forEach((filePartialConvo) => {
551
+ if (result.pconvos && result.pconvos.length > 0) {
552
+ result.pconvos.forEach((filePartialConvo) => {
542
553
  filePartialConvo.sourceTag = { convoDir, filename }
543
554
  if (!filePartialConvo.header.name) {
544
555
  filePartialConvo.header.name = filename
545
556
  }
546
557
  })
547
558
  }
548
- if (fileScriptingMemories && fileScriptingMemories.length) {
549
- fileScriptingMemories.forEach((scriptingMemory) => {
559
+ if (result.scriptingMemories && result.scriptingMemories.length > 0) {
560
+ result.scriptingMemories.forEach((scriptingMemory) => {
550
561
  scriptingMemory.sourceTag = { filename }
551
562
  })
552
563
  }
553
-
554
- if (fileUtterances) {
555
- this.fileUtterances = this._tagAndCleanupUtterances(fileUtterances, convoDir, filename)
564
+ if (result.utterances) {
565
+ result.utterances = this._tagAndCleanupUtterances(result.utterances, convoDir, filename)
556
566
  }
557
- return { convos: fileConvos, utterances: fileUtterances, pconvos: filePartialConvos, scriptingMemories: fileScriptingMemories }
567
+ return { convos: result.convos || [], utterances: result.utterances || [], pconvos: result.pconvos || [], scriptingMemories: result.scriptingMemories || [] }
558
568
  }
559
569
 
560
570
  _tagAndCleanupUtterances (utteranceFiles, convoDir, filename) {
561
571
  return utteranceFiles.map((fileUtt) => {
562
572
  fileUtt.sourceTag = { convoDir, filename }
563
- fileUtt.utterances = fileUtt.utterances
564
- .filter(u => u)
573
+ fileUtt.utterances = fileUtt.utterances.filter(u => u)
565
574
  return fileUtt
566
575
  })
567
576
  }
@@ -752,6 +761,7 @@ module.exports = class ScriptingProvider {
752
761
 
753
762
  debug(`ExpandScriptingMemoryToConvos - ${convosExpandedAll.length} convo expanded, added to convos (${this.convos.length}). Result ${convosExpandedAll.length + this.convos.length} convo`)
754
763
  this.convos = this.convos.concat(convosExpandedAll)
764
+ this._sortConvos()
755
765
  }
756
766
 
757
767
  ExpandUtterancesToConvos ({ useNameAsIntent, incomprehensionUtt } = {}) {
@@ -787,6 +797,11 @@ module.exports = class ScriptingProvider {
787
797
  conversation: [
788
798
  {
789
799
  sender: 'me',
800
+ logicHooks: [
801
+ {
802
+ name: 'SKIP_BOT_UNCONSUMED'
803
+ }
804
+ ],
790
805
  messageText: utt.name,
791
806
  stepTag: 'Step 1 - tell utterance'
792
807
  },
@@ -944,7 +959,7 @@ module.exports = class ScriptingProvider {
944
959
  }
945
960
  }
946
961
  } else {
947
- expandedConvos.push(Object.assign(_.cloneDeep(currentConvo), { conversation: convoStepsStack }))
962
+ expandedConvos.push(Object.assign(_.cloneDeep(currentConvo), { conversation: _.cloneDeep(convoStepsStack) }))
948
963
  }
949
964
  }
950
965
 
@@ -1,6 +1,8 @@
1
1
  const _ = require('lodash')
2
2
  const isJSON = require('is-json')
3
3
 
4
+ const { E_SCRIPTING_MEMORY_COLUMN_MODE } = require('../Enums')
5
+
4
6
  const normalizeText = (str, doCleanup) => {
5
7
  if (str && _.isArray(str)) {
6
8
  str = str.join(' ')
@@ -72,7 +74,7 @@ const toString = (value) => {
72
74
  }
73
75
 
74
76
  const flatString = (str) => {
75
- return str.split('\n').map(s => s.trim()).join(' ')
77
+ return str ? str.split('\n').map(s => s.trim()).join(' ') : ''
76
78
  }
77
79
 
78
80
  const linesToConvoStep = (lines, sender, context, eol, singleLineMode = false) => {
@@ -439,7 +441,7 @@ const convoStepToLines = (step) => {
439
441
  if (step.messageText) {
440
442
  lines.push((step.optional ? '?' : '') + (step.not ? '!' : '') + step.messageText)
441
443
  }
442
- if (step.buttons && step.buttons.length > 0) lines.push('BUTTONS ' + step.buttons.map(b => flatString(b.text)).join('|'))
444
+ if (step.buttons && step.buttons.length > 0) lines.push('BUTTONS ' + step.buttons.filter(b => b.text).map(b => flatString(b.text)).join('|'))
443
445
  if (step.media && step.media.length > 0) lines.push('MEDIA ' + step.media.filter(m => !m.buffer && m.mediaUri).map(m => m.mediaUri).join('|'))
444
446
  if (step.cards && step.cards.length > 0) {
445
447
  step.cards.forEach(c => {
@@ -449,7 +451,7 @@ const convoStepToLines = (step) => {
449
451
  if (c.content) cardTexts = cardTexts.concat(_.isArray(c.content) ? c.content : [c.content])
450
452
  if (cardTexts.length > 0) lines.push('CARDS ' + cardTexts.map(c => flatString(c)).join('|'))
451
453
 
452
- if (c.buttons && c.buttons.length > 0) lines.push('BUTTONS ' + c.buttons.map(b => flatString(b.text)).join('|'))
454
+ if (c.buttons && c.buttons.length > 0) lines.push('BUTTONS ' + c.buttons.filter(b => b.text).map(b => flatString(b.text)).join('|'))
453
455
  if (c.image && !c.image.buffer && c.image.mediaUri) lines.push('MEDIA ' + c.image.mediaUri)
454
456
  })
455
457
  }
@@ -463,6 +465,56 @@ const convoStepToLines = (step) => {
463
465
  return lines.map(l => l.trim())
464
466
  }
465
467
 
468
+ const linesToScriptingMemories = (lines, columnMode = null) => {
469
+ const guessScriptingMemoryColumnMode = (lines) => {
470
+ if (lines && lines.length > 1) {
471
+ if (lines[1].trim().startsWith('$')) return E_SCRIPTING_MEMORY_COLUMN_MODE.TESTCASENAMES
472
+ }
473
+ return E_SCRIPTING_MEMORY_COLUMN_MODE.VARNAMES
474
+ }
475
+ columnMode = columnMode || guessScriptingMemoryColumnMode(lines)
476
+
477
+ const scriptingMemories = []
478
+
479
+ if (columnMode === E_SCRIPTING_MEMORY_COLUMN_MODE.TESTCASENAMES) {
480
+ const caseNames = lines[0].split('|').map((name) => name.trim()).slice(1)
481
+
482
+ const varNames = []
483
+ const varValues = []
484
+ for (let row = 1; row < lines.length; row++) {
485
+ if (!lines[row] || lines[row].length === 0) continue
486
+ const rawRow = lines[row].split('|').map((name) => name.trim())
487
+ varNames.push(rawRow[0])
488
+ varValues.push(rawRow.slice(1))
489
+ }
490
+ for (let caseIndex = 0; caseIndex < caseNames.length; caseIndex++) {
491
+ const caseName = caseNames[caseIndex]
492
+
493
+ const values = varNames.reduce((agg, varName, varIndex) => {
494
+ agg[varName] = varValues[varIndex][caseIndex] || null
495
+ return agg
496
+ }, {})
497
+ const scriptingMemory = { header: { name: caseName }, values: values }
498
+ scriptingMemories.push(scriptingMemory)
499
+ }
500
+ } else {
501
+ const varNames = lines[0].split('|').map((name) => name.trim()).slice(1)
502
+ for (let row = 1; row < lines.length; row++) {
503
+ if (!lines[row] || lines[row].length === 0) continue
504
+ const rawRow = lines[row].split('|').map((name) => name.trim())
505
+ const caseName = rawRow[0]
506
+ const values = rawRow.slice(1)
507
+ const varValues = {}
508
+ for (let varIndex = 0; varIndex < varNames.length; varIndex++) {
509
+ varValues[varNames[varIndex]] = values[varIndex]
510
+ }
511
+ const scriptingMemory = { header: { name: caseName }, values: varValues }
512
+ scriptingMemories.push(scriptingMemory)
513
+ }
514
+ }
515
+ return scriptingMemories
516
+ }
517
+
466
518
  module.exports = {
467
519
  normalizeText,
468
520
  splitStringInNonEmptyLines,
@@ -475,5 +527,6 @@ module.exports = {
475
527
  convoStepToObject,
476
528
  validSenders,
477
529
  validateSender,
478
- validateConvo
530
+ validateConvo,
531
+ linesToScriptingMemories
479
532
  }
@@ -33,6 +33,10 @@ module.exports = {
33
33
  { name: 'TEXT_WILDCARD_ANY_IC', className: 'TextWildcardAnyICAsserter' },
34
34
  { name: 'TEXT_WILDCARD_ALL', className: 'TextWildcardAllAsserter' },
35
35
  { name: 'TEXT_WILDCARD_ALL_IC', className: 'TextWildcardAllICAsserter' },
36
+ { name: 'TEXT_WILDCARDEXACT_ANY', className: 'TextWildcardExactAnyAsserter' },
37
+ { name: 'TEXT_WILDCARDEXACT_ANY_IC', className: 'TextWildcardExactAnyICAsserter' },
38
+ { name: 'TEXT_WILDCARDEXACT_ALL', className: 'TextWildcardExactAllAsserter' },
39
+ { name: 'TEXT_WILDCARDEXACT_ALL_IC', className: 'TextWildcardExactAllICAsserter' },
36
40
  { name: 'TEXT_REGEXP_ANY', className: 'TextRegexpAnyAsserter' },
37
41
  { name: 'TEXT_REGEXP_ANY_IC', className: 'TextRegexpAnyICAsserter' },
38
42
  { name: 'TEXT_REGEXP_ALL', className: 'TextRegexpAllAsserter' },
@@ -240,6 +240,8 @@ module.exports = class LogicHookUtils {
240
240
  return CheckClass({ ref, ...this.buildScriptContext }, this.caps, args)
241
241
  } else if (isClass(CheckClass.PluginClass)) {
242
242
  return new CheckClass.PluginClass({ ref, ...this.buildScriptContext }, this.caps, args)
243
+ } else if (_.isFunction(CheckClass.PluginClass)) {
244
+ return CheckClass.PluginClass({ ref, ...this.buildScriptContext }, this.caps, args)
243
245
  } else {
244
246
  throw new Error(`${src} class or function expected`)
245
247
  }
@@ -118,7 +118,7 @@ module.exports = class JsonPathAsserter {
118
118
  }
119
119
  },
120
120
  cause: {
121
- expected: assert || (args && `any element for ${args.join('|')}`) || (!args && `any element in ${path}`),
121
+ expected: assert || ((args && args.length > 0) && `any element for ${args.join('|')}`) || ((!args || args.length === 0) && `any element in ${path}`),
122
122
  actual: null,
123
123
  path
124
124
  }
@@ -0,0 +1,8 @@
1
+ const BaseTextAsserter = require('./BaseTextAsserter')
2
+ const MatchFunctions = require('../../MatchFunctions')
3
+
4
+ module.exports = class TextWildcardExactAnyAsserter extends BaseTextAsserter {
5
+ constructor (context, caps = {}) {
6
+ super(context, caps, MatchFunctions.wildcardExact(false), 'all')
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ const BaseTextAsserter = require('./BaseTextAsserter')
2
+ const MatchFunctions = require('../../MatchFunctions')
3
+
4
+ module.exports = class TextWildcardExactAnyAsserter extends BaseTextAsserter {
5
+ constructor (context, caps = {}) {
6
+ super(context, caps, MatchFunctions.wildcardExact(true), 'all')
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ const BaseTextAsserter = require('./BaseTextAsserter')
2
+ const MatchFunctions = require('../../MatchFunctions')
3
+
4
+ module.exports = class TextWildcardExactAnyAsserter extends BaseTextAsserter {
5
+ constructor (context, caps = {}) {
6
+ super(context, caps, MatchFunctions.wildcardExact(false), 'any')
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ const BaseTextAsserter = require('./BaseTextAsserter')
2
+ const MatchFunctions = require('../../MatchFunctions')
3
+
4
+ module.exports = class TextWildcardExactAnyAsserter extends BaseTextAsserter {
5
+ constructor (context, caps = {}) {
6
+ super(context, caps, MatchFunctions.wildcardExact(true), 'any')
7
+ }
8
+ }
@@ -9,7 +9,7 @@ module.exports = class ClearQueueLogicHook {
9
9
  container._EmptyQueue()
10
10
  }
11
11
 
12
- onMeEnd ({ container }) {
12
+ onMeStart ({ container }) {
13
13
  container._EmptyQueue()
14
14
  }
15
15
 
@@ -20,6 +20,9 @@ module.exports = class MediaInput {
20
20
  }
21
21
 
22
22
  _getResolvedUri (uri, convo) {
23
+ if (uri.startsWith('data:')) {
24
+ return new url.URL(uri)
25
+ }
23
26
  if (this.globalArgs && this.globalArgs.baseUris) {
24
27
  const baseUrisSelector = _.get(convo, this.globalArgs.baseSelector || DEFAULT_BASE_SELECTOR)
25
28
  if (baseUrisSelector && this.globalArgs.baseUris[baseUrisSelector]) {
@@ -134,6 +137,8 @@ module.exports = class MediaInput {
134
137
  }
135
138
  })
136
139
  })
140
+ } else if (uri.protocol === 'data:') {
141
+ return Buffer.from(uri.href.split(',')[1], 'base64')
137
142
  }
138
143
  }
139
144
  }
@@ -142,6 +147,13 @@ module.exports = class MediaInput {
142
147
  return arg.indexOf('*') >= 0
143
148
  }
144
149
 
150
+ _mime (arg) {
151
+ if (arg.startsWith('data:')) {
152
+ return arg.substring(arg.indexOf(':') + 1, arg.indexOf(';'))
153
+ }
154
+ return mime.lookup(arg)
155
+ }
156
+
145
157
  expandConvo ({ convo, convoStep, args }) {
146
158
  const hasWildcard = args.findIndex(a => this._isWildcard(a)) >= 0
147
159
 
@@ -187,14 +199,14 @@ module.exports = class MediaInput {
187
199
  meMsg.media.push(new BotiumMockMedia({
188
200
  mediaUri: args[0],
189
201
  downloadUri: uri.toString(),
190
- mimeType: mime.lookup(args[0]),
202
+ mimeType: this._mime(args[0]),
191
203
  buffer
192
204
  }))
193
205
  }
194
206
  } else if (args.length === 2) {
195
207
  meMsg.media.push(new BotiumMockMedia({
196
208
  mediaUri: args[0],
197
- mimeType: mime.lookup(args[0]),
209
+ mimeType: this._mime(args[0]),
198
210
  buffer: args[1]
199
211
  }))
200
212
  }
@@ -0,0 +1,6 @@
1
+ hello
2
+
3
+ #me
4
+ hello
5
+
6
+ #bot
@@ -22,6 +22,12 @@ const myCapsPost = {
22
22
  [Capabilities.SIMPLEREST_BODY_TEMPLATE]: { BODY1: 'BODY1VALUE', BODY2: '{{msg.messageText}}' },
23
23
  [Capabilities.SIMPLEREST_RESPONSE_JSONPATH]: ['$']
24
24
  }
25
+ const myCapsFormPost = {
26
+ [Capabilities.CONTAINERMODE]: 'simplerest',
27
+ [Capabilities.SIMPLEREST_URL]: 'http://my-host.com/api/endpoint/{{msg.messageText}}',
28
+ [Capabilities.SIMPLEREST_METHOD]: 'POST',
29
+ [Capabilities.SIMPLEREST_RESPONSE_JSONPATH]: ['$']
30
+ }
25
31
 
26
32
  const myCapsScriptingMemory = {
27
33
  [Capabilities.CONTAINERMODE]: 'simplerest',
@@ -448,7 +454,7 @@ describe('connectors.simplerest.build', function () {
448
454
  assert.isTrue(err.message.includes('Cant load hook, syntax is not valid'))
449
455
  }
450
456
  })
451
- it('should query params from UPDATE_CUSTOM (without "?")', async function () {
457
+ it('should add query params from UPDATE_CUSTOM (without "?")', async function () {
452
458
  const myCaps = Object.assign({}, myCapsGet)
453
459
  const myMsg = Object.assign({}, msg)
454
460
  myMsg.ADD_QUERY_PARAM = {
@@ -467,7 +473,7 @@ describe('connectors.simplerest.build', function () {
467
473
 
468
474
  await container.Clean()
469
475
  })
470
- it('should query params from UPDATE_CUSTOM (with "?")', async function () {
476
+ it('should add query params from UPDATE_CUSTOM (with "?")', async function () {
471
477
  const myCaps = Object.assign({}, myCapsGet)
472
478
  myCaps.SIMPLEREST_URL = 'http://my-host.com/api/endpoint/messageText?const1=const1'
473
479
  const myMsg = Object.assign({}, msg)
@@ -510,6 +516,26 @@ describe('connectors.simplerest.build', function () {
510
516
 
511
517
  await container.Clean()
512
518
  })
519
+ it('should add form params from UPDATE_CUSTOM (without "?")', async function () {
520
+ const myCaps = Object.assign({}, myCapsFormPost)
521
+ const myMsg = Object.assign({}, msg)
522
+ myMsg.ADD_FORM_PARAM = {
523
+ formparam1: 'valueparam1',
524
+ formparam2: '{{msg.messageText}}'
525
+ }
526
+
527
+ const driver = new BotDriver(myCaps)
528
+ const container = await driver.Build()
529
+ assert.equal(container.pluginInstance.constructor.name, 'SimpleRestContainer')
530
+
531
+ await container.Start()
532
+ const request = await container.pluginInstance._buildRequest(myMsg)
533
+ assert.isObject(request.form)
534
+ assert.equal(request.form.formparam1, 'valueparam1')
535
+ assert.equal(request.form.formparam2, 'messageText')
536
+
537
+ await container.Clean()
538
+ })
513
539
  it('should add header from UPDATE_CUSTOM', async function () {
514
540
  const myCaps = Object.assign({}, myCapsGet)
515
541
  const myMsg = Object.assign({}, msg)
@@ -789,6 +815,7 @@ describe('connectors.simplerest.useresponse', function () {
789
815
  .get('/startencoded').reply(200, { text: JSON.stringify({ prop: 'response from start' }) })
790
816
  .get('/startstring').reply(200, 'response from start')
791
817
  .get('/msg').reply(200, { text: 'response from msg' })
818
+ .get('/msgfail').reply(400, { error: 'failure text' })
792
819
  .persist()
793
820
 
794
821
  const myCaps = Object.assign({
@@ -914,6 +941,19 @@ describe('connectors.simplerest.useresponse', function () {
914
941
  assert.equal(transcript.steps.length, 1)
915
942
  assert.equal(transcript.steps[0].actual.messageText, 'response from start')
916
943
  })
944
+
945
+ it('should use error body content', async function () {
946
+ await this.init({
947
+ [Capabilities.SIMPLEREST_URL]: 'https://mock.com/msgfail'
948
+ })
949
+
950
+ this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'hello.convo.txt')
951
+ try {
952
+ await this.compiler.convos[0].Run(this.container)
953
+ } catch (err) {
954
+ assert.isTrue(err.message.indexOf('failure text') >= 0)
955
+ }
956
+ })
917
957
  })
918
958
 
919
959
  describe('connectors.simplerest.inbound', function () {
@@ -0,0 +1,19 @@
1
+ continuefailing
2
+
3
+ #me
4
+ Hello
5
+
6
+ #bot
7
+ Hello
8
+
9
+ #me
10
+ Hello again!
11
+
12
+ #bot
13
+ Hello again FAILING!
14
+
15
+ #me
16
+ working again
17
+
18
+ #bot
19
+ working again
@@ -42,12 +42,25 @@ describe('convo.transcript', function () {
42
42
  this.compilerMultipleAssertErrors = this.driverMultipleAssertErrors.BuildCompiler()
43
43
  this.containerMultipleAssertErrors = await this.driverMultipleAssertErrors.Build()
44
44
  await this.containerMultipleAssertErrors.Start()
45
+
46
+ const myCapsSkipAssertErrors = {
47
+ [Capabilities.PROJECTNAME]: 'convo.transcript',
48
+ [Capabilities.CONTAINERMODE]: echoConnector,
49
+ [Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS]: true,
50
+ [Capabilities.SCRIPTING_ENABLE_SKIP_ASSERT_ERRORS]: true
51
+ }
52
+ this.driverSkipAssertErrors = new BotDriver(myCapsSkipAssertErrors)
53
+ this.compilerSkipAssertErrors = this.driverSkipAssertErrors.BuildCompiler()
54
+ this.containerSkipAssertErrors = await this.driverSkipAssertErrors.Build()
55
+ await this.containerSkipAssertErrors.Start()
45
56
  })
46
57
  afterEach(async function () {
47
58
  await this.container.Stop()
48
59
  await this.container.Clean()
49
60
  await this.containerMultipleAssertErrors.Stop()
50
61
  await this.containerMultipleAssertErrors.Clean()
62
+ await this.containerSkipAssertErrors.Stop()
63
+ await this.containerSkipAssertErrors.Clean()
51
64
  })
52
65
  it('should provide transcript steps on success', async function () {
53
66
  this.compiler.ReadScript(path.resolve(__dirname, 'convos'), '2steps.convo.txt')
@@ -347,4 +360,25 @@ describe('convo.transcript', function () {
347
360
  assert.equal(this.compilerMultipleAssertErrors.convos.length, 1)
348
361
  await this.compilerMultipleAssertErrors.convos[0].Run(this.containerMultipleAssertErrors)
349
362
  })
363
+ it('should continue on failing assertion', async function () {
364
+ this.compilerSkipAssertErrors.ReadScript(path.resolve(__dirname, 'convos'), 'continuefailing.convo.txt')
365
+ assert.equal(this.compilerSkipAssertErrors.convos.length, 1)
366
+
367
+ try {
368
+ await this.compilerSkipAssertErrors.convos[0].Run(this.containerSkipAssertErrors)
369
+ assert.fail('expected error')
370
+ } catch (err) {
371
+ assert.isDefined(err.transcript)
372
+ assert.equal(err.transcript.steps.length, 6)
373
+ assert.equal(err.transcript.steps[0].actual.messageText, this.compilerSkipAssertErrors.convos[0].conversation[0].messageText)
374
+ assert.equal(err.transcript.steps[1].actual.messageText, this.compilerSkipAssertErrors.convos[0].conversation[1].messageText)
375
+ assert.equal(err.transcript.steps[2].actual.messageText, this.compilerSkipAssertErrors.convos[0].conversation[2].messageText)
376
+ assert.equal(err.transcript.steps[3].expected.messageText, this.compilerSkipAssertErrors.convos[0].conversation[3].messageText)
377
+ assert.notEqual(err.transcript.steps[3].actual.messageText, this.compilerSkipAssertErrors.convos[0].conversation[3].messageText)
378
+ assert.isDefined(err.transcript.steps[3].err)
379
+ assert.equal(err.transcript.steps[4].expected.messageText, this.compilerSkipAssertErrors.convos[0].conversation[4].messageText)
380
+ assert.equal(err.transcript.steps[5].expected.messageText, this.compilerSkipAssertErrors.convos[0].conversation[5].messageText)
381
+ assert.isNull(err.transcript.steps[5].err)
382
+ }
383
+ })
350
384
  })
@@ -0,0 +1,7 @@
1
+ convos:
2
+ - name: text_wildcardexact_all_nok
3
+ steps:
4
+ - me:
5
+ - Im Joe, my number is 12345, and my ID is id1_123
6
+ - bot:
7
+ - TEXT_WILDCARDEXACT_ALL Im Joe, my number is 12345, and my ID is id2_*3|Im Joe, my number is 1*5, and my ID is id1_123
@@ -0,0 +1,7 @@
1
+ convos:
2
+ - name: text_wildcardexact_all_nok
3
+ steps:
4
+ - me:
5
+ - Im Joe, my number is 12345, and my ID is id1_123
6
+ - bot:
7
+ - TEXT_WILDCARDEXACT_ALL Im Joe, my number is 12345, and my ID is id1_*3|Im Joe, my number is 1*5, and my ID is id1_123
@@ -0,0 +1,7 @@
1
+ convos:
2
+ - name: text_wildcardexact_any_nok
3
+ steps:
4
+ - me:
5
+ - Im Joe, my number is 12345, and my ID is id1_123
6
+ - bot:
7
+ - TEXT_WILDCARDEXACT_ANY Im Joe, my number is 12345, and my ID is id2_*3|Im Joe, my number is 12345, and my ID is id3_*3
@@ -0,0 +1,7 @@
1
+ convos:
2
+ - name: text_wildcardexact_any_ok
3
+ steps:
4
+ - me:
5
+ - Im Joe, my number is 12345, and my ID is id1_123
6
+ - bot:
7
+ - TEXT_WILDCARDEXACT_ANY Im Joe, my number is 12345, and my ID is id1_*3|Im Joe, my number is 12345, and my ID is id2_*3
@@ -0,0 +1,51 @@
1
+ const assert = require('chai').assert
2
+ const path = require('path')
3
+
4
+ const BotDriver = require('../../..').BotDriver
5
+ const Capabilities = require('../../..').Capabilities
6
+
7
+ const echoConnector = ({ queueBotSays }) => {
8
+ return {
9
+ UserSays (msg) {
10
+ const botMsg = { sender: 'bot', sourceData: msg.sourceData, messageText: msg.messageText }
11
+ queueBotSays(botMsg)
12
+ }
13
+ }
14
+ }
15
+
16
+ describe('scripting.asserters.textWildcardExactAllAsserter', function () {
17
+ beforeEach(async function () {
18
+ const myCaps = {
19
+ [Capabilities.PROJECTNAME]: 'scripting.asserters.textWildcardExactAllAsserter',
20
+ [Capabilities.CONTAINERMODE]: echoConnector
21
+ }
22
+ const driver = new BotDriver(myCaps)
23
+ this.compiler = driver.BuildCompiler()
24
+ this.container = await driver.Build()
25
+ })
26
+ afterEach(async function () {
27
+ this.container && await this.container.Clean()
28
+ })
29
+
30
+ it('ok', async function () {
31
+ this.compiler.ReadScriptsFromDirectory(path.resolve(__dirname, 'convos', 'text_wildcardexact_all_ok.yml'))
32
+
33
+ this.compiler.ExpandScriptingMemoryToConvos()
34
+ assert.equal(this.compiler.convos.length, 1)
35
+ await this.compiler.convos[0].Run(this.container)
36
+ })
37
+
38
+ it('nok', async function () {
39
+ this.compiler.ReadScriptsFromDirectory(path.resolve(__dirname, 'convos', 'text_wildcardexact_all_nok.yml'))
40
+
41
+ this.compiler.ExpandScriptingMemoryToConvos()
42
+ assert.equal(this.compiler.convos.length, 1)
43
+
44
+ try {
45
+ await this.compiler.convos[0].Run(this.container)
46
+ assert.fail('expected error')
47
+ } catch (err) {
48
+ assert.equal(err.message, 'text_wildcardexact_all_nok/Line 2: assertion error - Line 2: Expected text(s) in response "Im Joe, my number is 12345, and my ID is id2_*3"')
49
+ }
50
+ })
51
+ })