botium-core 1.14.8 → 1.14.10

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botium-core",
3
- "version": "1.14.8",
3
+ "version": "1.14.10",
4
4
  "description": "The Selenium for Chatbots",
5
5
  "main": "index.js",
6
6
  "module": "dist/botium-es.js",
@@ -63,6 +63,7 @@
63
63
  "socketio-auth": "^0.1.1",
64
64
  "swagger-jsdoc": "^6.2.8",
65
65
  "swagger-ui-express": "^5.0.0",
66
+ "tinyglobby": "^0.2.10",
66
67
  "uuid": "^9.0.1",
67
68
  "word-error-rate": "0.0.7",
68
69
  "write-yaml": "^1.0.0",
@@ -277,6 +277,10 @@ class Convo {
277
277
  let skipTranscriptStep = false
278
278
  let conditionalGroupId = null
279
279
  let conditionMetInGroup = false
280
+ let skipOptionalStep = false
281
+ // If there are optional step(s) in the conversation, and the message from the bot fails on each optional bot step(s) and/or mandatory bot step, then we have an unexpected message.
282
+ // So in this case an unexpected error should be shown instead of the latest assertion error.
283
+ let optionalStepAssertionError = false
280
284
  let globalConvoStepParameters = container.caps[Capabilities.SCRIPTING_CONVO_STEP_PARAMETERS] || {}
281
285
  let retryBotMessageTimeoutEnd = null
282
286
  let retryBotMessageConvoId = null
@@ -284,6 +288,13 @@ class Convo {
284
288
  for (let i = 0; i < this.conversation.length; i = (retryBotMessageDropBotResponse ? i : i + 1)) {
285
289
  retryBotMessageDropBotResponse = false
286
290
  const convoStep = this.conversation[i]
291
+ if (!convoStep.optional) {
292
+ skipOptionalStep = false
293
+ }
294
+ if (convoStep.optional && skipOptionalStep) {
295
+ // If there are multiple optional steps, and the previous optional step was timeout, then the next optional step should be skipped to prevent too long convo run with multiple timeout.
296
+ continue
297
+ }
287
298
  const rawConvoStepParameters = convoStep.logicHooks.find(lh => lh.name === 'CONVO_STEP_PARAMETERS')?.args
288
299
  let convoStepParameters = {}
289
300
  if (rawConvoStepParameters && rawConvoStepParameters.length) {
@@ -416,7 +427,7 @@ class Convo {
416
427
  await this.scriptingEvents.onBotStart({ convo: this, convoStep, container, scriptingMemory, transcript, transcriptStep })
417
428
  transcriptStep.botBegin = new Date()
418
429
  if (!botMsg) {
419
- botMsg = await container.WaitBotSays(convoStep.channel)
430
+ botMsg = await container.WaitBotSays(convoStep.channel, convoStepParameters?.stepTimeout)
420
431
  }
421
432
  transcriptStep.botEnd = new Date()
422
433
  transcriptStep.actual = new BotiumMockMessage(botMsg)
@@ -426,6 +437,11 @@ class Convo {
426
437
  } catch (err) {
427
438
  transcriptStep.botEnd = new Date()
428
439
 
440
+ if (!(err.message.indexOf('Bot did not respond within') < 0) && convoStep.optional) {
441
+ skipOptionalStep = true
442
+ continue
443
+ }
444
+
429
445
  const failErr = botiumErrorFromErr(`${this.header.name}/${convoStep.stepTag}: error waiting for bot - ${err.message}`, err)
430
446
  debug(failErr)
431
447
  try {
@@ -508,6 +524,7 @@ class Convo {
508
524
  }
509
525
  waitForBotSays = false
510
526
  skipTranscriptStep = true
527
+ optionalStepAssertionError = true
511
528
  return true
512
529
  } else if (retryOn) {
513
530
  if (!retryBotMessageTimeoutEnd || retryBotMessageConvoId !== convoStep.stepTag) {
@@ -527,9 +544,18 @@ class Convo {
527
544
  }
528
545
 
529
546
  if (container.caps[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS]) {
530
- assertErrors.push(err)
547
+ if (optionalStepAssertionError) {
548
+ optionalStepAssertionError = false
549
+ assertErrors.push(new BotiumError(`${this.header.name}: Unexpected message.`))
550
+ } else {
551
+ assertErrors.push(err)
552
+ }
531
553
  return false
532
554
  } else {
555
+ if (optionalStepAssertionError) {
556
+ optionalStepAssertionError = false
557
+ throw new BotiumError(`${this.header.name}: Unexpected message.`)
558
+ }
533
559
  throw err
534
560
  }
535
561
  }
@@ -543,6 +569,7 @@ class Convo {
543
569
  if (convoStep.not) {
544
570
  try {
545
571
  this.scriptingEvents.assertBotNotResponse(response, tomatch, `${this.header.name}/${convoStep.stepTag}`, lastMeConvoStep, convoStepParameters)
572
+ optionalStepAssertionError = false
546
573
  } catch (err) {
547
574
  if (isErrorHandledWithOptionConvoStep(err)) {
548
575
  continue
@@ -551,6 +578,7 @@ class Convo {
551
578
  } else {
552
579
  try {
553
580
  this.scriptingEvents.assertBotResponse(response, tomatch, `${this.header.name}/${convoStep.stepTag}`, lastMeConvoStep, convoStepParameters)
581
+ optionalStepAssertionError = false
554
582
  } catch (err) {
555
583
  if (isErrorHandledWithOptionConvoStep(err)) {
556
584
  continue
@@ -560,6 +588,7 @@ class Convo {
560
588
  } else if (convoStep.sourceData) {
561
589
  try {
562
590
  this._compareObject(container, scriptingMemory, convoStep, botMsg.sourceData, convoStep.sourceData, botMsg, convoStepParameters)
591
+ optionalStepAssertionError = false
563
592
  } catch (err) {
564
593
  if (isErrorHandledWithOptionConvoStep(err)) {
565
594
  continue
@@ -570,11 +599,13 @@ class Convo {
570
599
  try {
571
600
  await this.scriptingEvents.assertConvoStep({ convo: this, convoStep, container, scriptingMemory, botMsg, transcript, transcriptStep })
572
601
  await this.scriptingEvents.onBotEnd({ convo: this, convoStep, container, scriptingMemory, botMsg, transcript, transcriptStep })
602
+ optionalStepAssertionError = false
573
603
  } catch (err) {
574
604
  const nextConvoStep = this.conversation[i + 1]
575
605
  if (convoStep.optional && nextConvoStep && nextConvoStep.sender === 'bot') {
576
606
  waitForBotSays = false
577
607
  skipTranscriptStep = true
608
+ optionalStepAssertionError = true
578
609
  continue
579
610
  }
580
611
 
@@ -607,8 +638,17 @@ class Convo {
607
638
  } catch (failErr) {
608
639
  }
609
640
  if (container.caps[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS] && err instanceof BotiumError) {
610
- assertErrors.push(err)
641
+ if (optionalStepAssertionError) {
642
+ optionalStepAssertionError = false
643
+ assertErrors.push(new BotiumError(`${this.header.name}: Unexpected message.`))
644
+ } else {
645
+ assertErrors.push(err)
646
+ }
611
647
  } else {
648
+ if (optionalStepAssertionError) {
649
+ optionalStepAssertionError = false
650
+ throw new BotiumError(`${this.header.name}: Unexpected message.`)
651
+ }
612
652
  throw failErr
613
653
  }
614
654
  }
@@ -83,9 +83,9 @@ module.exports = class ScriptingProvider {
83
83
  this.utterances = {}
84
84
  this.matchFn = null
85
85
  this.asserters = {}
86
- this.globalAsserter = {}
86
+ this.globalAsserters = {}
87
87
  this.logicHooks = {}
88
- this.globalLogicHook = {}
88
+ this.globalLogicHooks = {}
89
89
  this.userInputs = {}
90
90
  this.partialConvos = {}
91
91
  this.scriptingMemories = []
@@ -319,8 +319,9 @@ module.exports = class ScriptingProvider {
319
319
  }
320
320
  }
321
321
 
322
- const convoAsserter = asserters
323
- .filter(a => this.asserters[a.name][asserterType])
322
+ const localAsserters = (asserters || []).filter(a => this.asserters[a.name][asserterType])
323
+
324
+ const convoStepPromises = localAsserters
324
325
  .map(a => ({
325
326
  asserter: a,
326
327
  promise: callAsserter(a, this.asserters[a.name], {
@@ -335,8 +336,12 @@ module.exports = class ScriptingProvider {
335
336
  }))
336
337
  .map(({ promise, asserter }) => updateExceptionContext(promise, asserter))
337
338
 
338
- const globalAsserter = Object.values(this.globalAsserter)
339
+ const globalAsserters = Object.keys(this.globalAsserters)
340
+ .filter(name => localAsserters.map(a => a.name).indexOf(name) < 0)
341
+ .reduce((agg, name) => [...agg, this.globalAsserters[name]], [])
339
342
  .filter(a => a[asserterType])
343
+
344
+ const globalPromises = globalAsserters
340
345
  .map(a => ({
341
346
  asserter: a,
342
347
  promise: p(this.retryHelperAsserter, () => a[asserterType]({
@@ -351,7 +356,7 @@ module.exports = class ScriptingProvider {
351
356
  }))
352
357
  .map(({ promise, asserter }) => updateExceptionContext(promise, asserter))
353
358
 
354
- const allPromises = [...convoAsserter, ...globalAsserter]
359
+ const allPromises = [...convoStepPromises, ...globalPromises]
355
360
  if (this.caps[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS]) {
356
361
  return Promise.allSettled(allPromises).then((results) => {
357
362
  const rejected = results.filter(result => result.status === 'rejected').map(result => result.reason)
@@ -372,8 +377,7 @@ module.exports = class ScriptingProvider {
372
377
  throw Error(`Unknown hookType ${hookType}`)
373
378
  }
374
379
 
375
- const localHooks = (logicHooks || [])
376
- .filter(l => this.logicHooks[l.name][hookType])
380
+ const localHooks = (logicHooks || []).filter(l => this.logicHooks[l.name][hookType])
377
381
 
378
382
  const convoStepPromises = localHooks
379
383
  .map(l => p(this.retryHelperLogicHook, () => this.logicHooks[l.name][hookType]({
@@ -386,7 +390,10 @@ module.exports = class ScriptingProvider {
386
390
  ...rest
387
391
  })))
388
392
 
389
- const globalHooks = Object.values(this.globalLogicHook).filter(l => l[hookType])
393
+ const globalHooks = Object.keys(this.globalLogicHooks)
394
+ .filter(name => localHooks.map(l => l.name).indexOf(name) < 0)
395
+ .reduce((agg, name) => [...agg, this.globalLogicHooks[name]], [])
396
+ .filter(l => l[hookType])
390
397
  const globalPromises = globalHooks.map(l => p(this.retryHelperLogicHook, () => l[hookType]({ convo, convoStep, scriptingMemory, container, args: [], isGlobal: true, ...rest })))
391
398
 
392
399
  const allPromises = [...convoStepPromises, ...globalPromises]
@@ -499,9 +506,9 @@ module.exports = class ScriptingProvider {
499
506
 
500
507
  const logicHookUtils = new LogicHookUtils({ buildScriptContext: this._buildScriptContext(), caps: this.caps })
501
508
  this.asserters = logicHookUtils.asserters
502
- this.globalAsserter = logicHookUtils.getGlobalAsserter()
509
+ this.globalAsserters = logicHookUtils.getGlobalAsserters()
503
510
  this.logicHooks = logicHookUtils.logicHooks
504
- this.globalLogicHook = logicHookUtils.getGlobalLogicHook()
511
+ this.globalLogicHooks = logicHookUtils.getGlobalLogicHooks()
505
512
  this.userInputs = logicHookUtils.userInputs
506
513
  }
507
514
 
@@ -404,13 +404,6 @@ const validateConvo = (convo) => {
404
404
  if (optionalSet.size > 1) {
405
405
  validationResult.errors.push(new Error(`Step ${i + 1}: Failed to decompile conversation. Mixed optional flag is not allowed inside one step.`))
406
406
  }
407
-
408
- if (optionalSet.size === 1 && optionalSet.has(true)) {
409
- const nextStep = convo.conversation[i + 1]
410
- if (!nextStep || nextStep.sender !== 'bot') {
411
- validationResult.errors.push(new Error(`Step ${i + 1}: Optional bot convo step has to be followed by a bot convo step.`))
412
- }
413
- }
414
407
  }
415
408
  if (!validateSender(step.sender)) {
416
409
  validationResult.errors.push(new Error(`Step ${i + 1}: Sender #${step.sender} is invalid.`))
@@ -456,6 +449,10 @@ const convoStepToLines = (step) => {
456
449
  } else {
457
450
  if (step.messageText) {
458
451
  lines.push((step.optional ? '?' : '') + (step.not ? '!' : '') + step.messageText)
452
+ } else {
453
+ if (step.optional) {
454
+ lines.push('?')
455
+ }
459
456
  }
460
457
  if (step.buttons && step.buttons.length > 0) lines.push('BUTTONS' + _formatAppendArgs(step.buttons.filter(b => b.text).map(b => flatString(b.text))))
461
458
  if (step.media && step.media.length > 0) lines.push('MEDIA' + _formatAppendArgs(step.media.filter(m => !m.buffer && m.mediaUri).map(m => m.mediaUri)))
@@ -24,9 +24,9 @@ const _ = require('lodash')
24
24
  module.exports = class LogicHookUtils {
25
25
  constructor ({ buildScriptContext, caps }) {
26
26
  this.asserters = {}
27
- this.globalAsserters = []
27
+ this.globalAsserterNames = []
28
28
  this.logicHooks = {}
29
- this.globalLogicHooks = []
29
+ this.globalLogicHookNames = []
30
30
  this.userInputs = {}
31
31
  this.buildScriptContext = buildScriptContext
32
32
  this.caps = caps
@@ -64,7 +64,7 @@ module.exports = class LogicHookUtils {
64
64
  }
65
65
  this.asserters[asserter.ref] = this._loadClass(asserter, 'asserter')
66
66
  if (asserter.global) {
67
- this.globalAsserters.push(asserter.ref)
67
+ this.globalAsserterNames.push(asserter.ref)
68
68
  }
69
69
  })
70
70
  }
@@ -77,7 +77,7 @@ module.exports = class LogicHookUtils {
77
77
  }
78
78
  this.logicHooks[logicHook.ref] = this._loadClass(logicHook, 'logichook')
79
79
  if (logicHook.global) {
80
- this.globalLogicHooks.push(logicHook.ref)
80
+ this.globalLogicHookNames.push(logicHook.ref)
81
81
  }
82
82
  })
83
83
  }
@@ -92,14 +92,12 @@ module.exports = class LogicHookUtils {
92
92
  })
93
93
  }
94
94
 
95
- getGlobalAsserter () {
96
- return this.globalAsserters
97
- .map(name => this.asserters[name])
95
+ getGlobalAsserters () {
96
+ return this.globalAsserterNames.reduce((agg, name) => ({ ...agg, [name]: this.asserters[name] }), {})
98
97
  }
99
98
 
100
- getGlobalLogicHook () {
101
- return this.globalLogicHooks
102
- .map(name => this.logicHooks[name])
99
+ getGlobalLogicHooks () {
100
+ return this.globalLogicHookNames.reduce((agg, name) => ({ ...agg, [name]: this.logicHooks[name] }), {})
103
101
  }
104
102
 
105
103
  _loadClass ({ src, ref, args }, hookType) {
@@ -1,6 +1,6 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
- const globby = require('globby')
3
+ const globSync = require('tinyglobby').globSync
4
4
  const request = require('request')
5
5
  const mime = require('mime-types')
6
6
  const url = require('url')
@@ -161,7 +161,7 @@ module.exports = class MediaInput {
161
161
  const baseDir = this._getBaseDir(convo)
162
162
  return args.reduce((e, arg) => {
163
163
  if (this._isWildcard(arg)) {
164
- const mediaFiles = globby.sync(arg, { cwd: baseDir, gitignore: true })
164
+ const mediaFiles = globSync(arg, { cwd: baseDir })
165
165
  mediaFiles.forEach(mf => {
166
166
  e.push({
167
167
  name: 'MEDIA',
@@ -0,0 +1,22 @@
1
+ welcome multiple botsteps opt
2
+
3
+ #me
4
+ Welcome
5
+
6
+ #bot
7
+ Welcome
8
+
9
+ #bot
10
+ ?Select an option:
11
+ CONVO_STEP_PARAMETERS {"stepTimeout": 300}
12
+
13
+ #bot
14
+ ?
15
+ ?BUTTONS First Button|Second Button
16
+ CONVO_STEP_PARAMETERS {"stepTimeout": 300}
17
+
18
+ #me
19
+ Thanks
20
+
21
+ #bot
22
+ Thanks
@@ -21,6 +21,85 @@ const echoConnector = ({ queueBotSays }) => {
21
21
  }
22
22
  }
23
23
 
24
+ const echoConnectorMultipleBotMessages = ({ queueBotSays }) => {
25
+ return {
26
+ UserSays (msg) {
27
+ const botMsg = { sender: 'bot', sourceData: msg.sourceData, messageText: msg.messageText }
28
+ if (msg.messageText === 'Welcome') {
29
+ botMsg.messageText = 'Welcome'
30
+ queueBotSays(botMsg)
31
+
32
+ setTimeout(() => {
33
+ botMsg.messageText = 'Select an option:'
34
+ queueBotSays(botMsg)
35
+ }, 200)
36
+
37
+ setTimeout(() => {
38
+ botMsg.messageText = ''
39
+ botMsg.buttons = [
40
+ { text: 'First Button' },
41
+ { text: 'Second Button' }
42
+ ]
43
+ queueBotSays(botMsg)
44
+ }, 200)
45
+ } else {
46
+ queueBotSays(botMsg)
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ const echoConnectorMultipleBotMessagesSkipAnOptional = ({ queueBotSays }) => {
53
+ return {
54
+ UserSays (msg) {
55
+ const botMsg = { sender: 'bot', sourceData: msg.sourceData, messageText: msg.messageText }
56
+ if (msg.messageText === 'Welcome') {
57
+ botMsg.messageText = 'Welcome'
58
+ queueBotSays(botMsg)
59
+
60
+ setTimeout(() => {
61
+ botMsg.messageText = ''
62
+ botMsg.buttons = [
63
+ { text: 'First Button' },
64
+ { text: 'Second Button' }
65
+ ]
66
+ queueBotSays(botMsg)
67
+ }, 200)
68
+ } else {
69
+ queueBotSays(botMsg)
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ const echoConnectorMultipleBotMessagesAssertionFailOnOptional = ({ queueBotSays }) => {
76
+ return {
77
+ UserSays (msg) {
78
+ const botMsg = { sender: 'bot', sourceData: msg.sourceData, messageText: msg.messageText }
79
+ if (msg.messageText === 'Welcome') {
80
+ botMsg.messageText = 'Welcome'
81
+ queueBotSays(botMsg)
82
+
83
+ setTimeout(() => {
84
+ botMsg.messageText = 'Fail on this:'
85
+ queueBotSays(botMsg)
86
+ }, 200)
87
+
88
+ setTimeout(() => {
89
+ botMsg.messageText = ''
90
+ botMsg.buttons = [
91
+ { text: 'First Button' },
92
+ { text: 'Second Button' }
93
+ ]
94
+ queueBotSays(botMsg)
95
+ }, 200)
96
+ } else {
97
+ queueBotSays(botMsg)
98
+ }
99
+ }
100
+ }
101
+ }
102
+
24
103
  describe('convo.transcript', function () {
25
104
  beforeEach(async function () {
26
105
  const myCaps = {
@@ -54,6 +133,36 @@ describe('convo.transcript', function () {
54
133
  this.compilerSkipAssertErrors = this.driverSkipAssertErrors.BuildCompiler()
55
134
  this.containerSkipAssertErrors = await this.driverSkipAssertErrors.Build()
56
135
  await this.containerSkipAssertErrors.Start()
136
+
137
+ const myCapsMultipleBotMessages = {
138
+ [Capabilities.PROJECTNAME]: 'convo.transcript',
139
+ [Capabilities.CONTAINERMODE]: echoConnectorMultipleBotMessages,
140
+ [Capabilities.SCRIPTING_FORCE_BOT_CONSUMED]: true
141
+ }
142
+ this.driverMultipleBotmessages = new BotDriver(myCapsMultipleBotMessages)
143
+ this.compilerMultipleBotmessages = this.driverMultipleBotmessages.BuildCompiler()
144
+ this.containerMultipleBotmessages = await this.driverMultipleBotmessages.Build()
145
+ await this.containerMultipleBotmessages.Start()
146
+
147
+ const myCapsMultipleBotMessagesSkipAnOptional = {
148
+ [Capabilities.PROJECTNAME]: 'convo.transcript',
149
+ [Capabilities.CONTAINERMODE]: echoConnectorMultipleBotMessagesSkipAnOptional,
150
+ [Capabilities.SCRIPTING_FORCE_BOT_CONSUMED]: true
151
+ }
152
+ this.driverMultipleBotmessagesSkipAnOptional = new BotDriver(myCapsMultipleBotMessagesSkipAnOptional)
153
+ this.compilerMultipleBotmessagesSkipAnOptional = this.driverMultipleBotmessagesSkipAnOptional.BuildCompiler()
154
+ this.containerMultipleBotmessagesSkipAnOptional = await this.driverMultipleBotmessagesSkipAnOptional.Build()
155
+ await this.containerMultipleBotmessagesSkipAnOptional.Start()
156
+
157
+ const myCapsMultipleBotMessagesAssertionFailOnOptional = {
158
+ [Capabilities.PROJECTNAME]: 'convo.transcript',
159
+ [Capabilities.CONTAINERMODE]: echoConnectorMultipleBotMessagesAssertionFailOnOptional,
160
+ [Capabilities.SCRIPTING_FORCE_BOT_CONSUMED]: true
161
+ }
162
+ this.driverMultipleBotmessagesAssertionFailOnOptional = new BotDriver(myCapsMultipleBotMessagesAssertionFailOnOptional)
163
+ this.compilerMultipleBotmessagesAssertionFailOnOptional = this.driverMultipleBotmessagesAssertionFailOnOptional.BuildCompiler()
164
+ this.containerMultipleBotmessagesAssertionFailOnOptional = await this.driverMultipleBotmessagesAssertionFailOnOptional.Build()
165
+ await this.containerMultipleBotmessagesAssertionFailOnOptional.Start()
57
166
  })
58
167
  afterEach(async function () {
59
168
  await this.container.Stop()
@@ -143,6 +252,43 @@ describe('convo.transcript', function () {
143
252
  assert.isDefined(transcript)
144
253
  assert.equal(transcript.steps.length, 2)
145
254
  })
255
+ it('should provide transcript optional multiple bot steps on getting all bot messages', async function () {
256
+ this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'welcome_multiple_botsteps_opt.convo.txt')
257
+ assert.equal(this.compiler.convos.length, 1)
258
+
259
+ const transcript = await this.compiler.convos[0].Run(this.containerMultipleBotmessages)
260
+ assert.isDefined(transcript)
261
+ assert.equal(transcript.steps.length, 6)
262
+ })
263
+ it('should provide transcript optional multiple bot steps on skip an optional bot messages', async function () {
264
+ this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'welcome_multiple_botsteps_opt.convo.txt')
265
+ assert.equal(this.compiler.convos.length, 1)
266
+
267
+ const transcript = await this.compiler.convos[0].Run(this.containerMultipleBotmessagesSkipAnOptional)
268
+ assert.isDefined(transcript)
269
+ assert.equal(transcript.steps.length, 5)
270
+ })
271
+ it('should provide transcript optional multiple bot steps assertion fail on optional bot messages', async function () {
272
+ this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'welcome_multiple_botsteps_opt.convo.txt')
273
+ assert.equal(this.compiler.convos.length, 1)
274
+
275
+ try {
276
+ await this.compiler.convos[0].Run(this.containerMultipleBotmessagesAssertionFailOnOptional)
277
+ assert.fail('expected error')
278
+ } catch (err) {
279
+ assert.isDefined(err.transcript)
280
+ assert.equal(err.transcript.steps.length, 3)
281
+ assert.isTrue(err.message.includes('Unexpected message'))
282
+ }
283
+ })
284
+ it('should provide transcript optional multiple bot steps on not getting all bot messages', async function () {
285
+ this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'welcome_multiple_botsteps_opt.convo.txt')
286
+ assert.equal(this.compiler.convos.length, 1)
287
+
288
+ const transcript = await this.compiler.convos[0].Run(this.container)
289
+ assert.isDefined(transcript)
290
+ assert.equal(transcript.steps.length, 5)
291
+ })
146
292
  it('should include pause in transcript steps', async function () {
147
293
  this.compiler.ReadScript(path.resolve(__dirname, 'convos'), '2stepsWithPause.convo.txt')
148
294
  assert.equal(this.compiler.convos.length, 1)
@@ -84,6 +84,12 @@ describe('scripting.asserters.convoStepParametersForAssert', function () {
84
84
 
85
85
  await this.compiler.convos[0].Run(this.container)
86
86
  })
87
+ it('should skip optional bot message', async function () {
88
+ this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_optional_with_timeout.convo.txt')
89
+ assert.equal(this.compiler.convos.length, 1)
90
+
91
+ await this.compiler.convos[0].Run(this.container)
92
+ })
87
93
  it('should retry until succesful asserters', async function () {
88
94
  this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_asserters_good.convo.txt')
89
95
  assert.equal(this.compiler.convos.length, 1)
@@ -0,0 +1,16 @@
1
+ convo_step_parameter_retry_main_good
2
+
3
+ #me
4
+ Hello
5
+
6
+ #bot
7
+ You said Hello
8
+
9
+ #bot
10
+ ?Wrong answer
11
+
12
+ #me
13
+ Hello2
14
+
15
+ #bot
16
+ You said Hello2
@@ -0,0 +1,105 @@
1
+ const Constants = require('../../../src/scripting/Constants')
2
+ const assert = require('chai').assert
3
+ const BotDriver = require('../../..').BotDriver
4
+ const Capabilities = require('../../..').Capabilities
5
+
6
+ const echoConnector = () => ({ queueBotSays }) => {
7
+ return {
8
+ UserSays (msg) {
9
+ const botMsg = { sender: 'bot', sourceData: msg.sourceData, messageText: msg.messageText }
10
+ queueBotSays(botMsg)
11
+ }
12
+ }
13
+ }
14
+
15
+ const buildDriver = async (mergeCaps) => {
16
+ const myCaps = Object.assign({
17
+ [Capabilities.PROJECTNAME]: 'convo.localvsglobal',
18
+ [Capabilities.CONTAINERMODE]: echoConnector()
19
+ }, mergeCaps)
20
+
21
+ const result = {}
22
+ result.driver = new BotDriver(myCaps)
23
+ result.compiler = result.driver.BuildCompiler()
24
+ result.container = await result.driver.Build()
25
+ return result
26
+ }
27
+
28
+ const convoScriptAsserters = `
29
+ LOCALVSGLOBAL
30
+
31
+ #me
32
+ hello
33
+
34
+ #bot
35
+
36
+ #me
37
+ hello 2
38
+
39
+ #bot
40
+ MYASSERTER
41
+ `
42
+
43
+ const convoScriptHooks = `
44
+ LOCALVSGLOBAL
45
+
46
+ #me
47
+ hello
48
+
49
+ #bot
50
+
51
+ #me
52
+ hello 2
53
+
54
+ #bot
55
+ MYHOOK
56
+ `
57
+
58
+ describe('Using local and global hooks together', function () {
59
+ it('should use local and global asserter', async function () {
60
+ let localAssertionCount = 0
61
+ let globalAssertionCount = 0
62
+
63
+ const { compiler, container } = await buildDriver({
64
+ [Capabilities.ASSERTERS]: [{
65
+ ref: 'MYASSERTER',
66
+ src: {
67
+ assertConvoStep: ({ isGlobal }) => {
68
+ if (isGlobal) globalAssertionCount++
69
+ else localAssertionCount++
70
+ return Promise.resolve()
71
+ }
72
+ },
73
+ global: true
74
+ }]
75
+ })
76
+
77
+ compiler.ReadScriptFromBuffer(Buffer.from(convoScriptAsserters), Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_CONVO)
78
+ await compiler.convos[0].Run(container)
79
+ assert.equal(localAssertionCount, 1)
80
+ assert.equal(globalAssertionCount, 1)
81
+ })
82
+ it('should use local and global logic hooks', async function () {
83
+ let localHookCount = 0
84
+ let globalHookCount = 0
85
+
86
+ const { compiler, container } = await buildDriver({
87
+ [Capabilities.LOGIC_HOOKS]: [{
88
+ ref: 'MYHOOK',
89
+ src: {
90
+ onBotEnd: ({ isGlobal }) => {
91
+ if (isGlobal) globalHookCount++
92
+ else localHookCount++
93
+ return Promise.resolve()
94
+ }
95
+ },
96
+ global: true
97
+ }]
98
+ })
99
+
100
+ compiler.ReadScriptFromBuffer(Buffer.from(convoScriptHooks), Constants.SCRIPTING_FORMAT_TXT, Constants.SCRIPTING_TYPE_CONVO)
101
+ await compiler.convos[0].Run(container)
102
+ assert.equal(localHookCount, 1)
103
+ assert.equal(globalHookCount, 1)
104
+ })
105
+ })
@@ -126,31 +126,6 @@ botText2
126
126
  `
127
127
  )
128
128
  })
129
- it('should fail decompile convo with optional step not followed bot step', async function () {
130
- const scriptingProvider = new ScriptingProvider(DefaultCapabilities)
131
- await scriptingProvider.Build()
132
-
133
- const convo = {
134
- header: {
135
- name: 'test convo'
136
- },
137
- conversation: [
138
- {
139
- sender: 'bot',
140
- messageText: 'botText',
141
- not: true,
142
- optional: true
143
- }
144
- ]
145
- }
146
-
147
- try {
148
- scriptingProvider.Decompile([convo], 'SCRIPTING_FORMAT_TXT')
149
- assert.fail('expected error')
150
- } catch (err) {
151
- assert.equal(err.message, 'Step 1: Optional bot convo step has to be followed by a bot convo step.')
152
- }
153
- })
154
129
  it('should fail decompile convo with mixed optional step', async function () {
155
130
  const scriptingProvider = new ScriptingProvider(DefaultCapabilities)
156
131
  await scriptingProvider.Build()