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/README.md +0 -4
- package/dist/botium-cjs.js +72 -26
- package/dist/botium-cjs.js.map +1 -1
- package/dist/botium-es.js +72 -26
- package/dist/botium-es.js.map +1 -1
- package/package.json +2 -1
- package/src/scripting/Convo.js +43 -3
- package/src/scripting/ScriptingProvider.js +18 -11
- package/src/scripting/helper.js +4 -7
- package/src/scripting/logichook/LogicHookUtils.js +8 -10
- package/src/scripting/logichook/userinput/MediaInput.js +2 -2
- package/test/convo/convos/welcome_multiple_botsteps_opt.convo.txt +22 -0
- package/test/convo/transcript.spec.js +146 -0
- package/test/scripting/asserters/convoStepParameters.spec.js +6 -0
- package/test/scripting/asserters/convos/convo_step_parameter_optional_with_timeout.convo.txt +16 -0
- package/test/scripting/logichooks/localvsglobal.spec.js +105 -0
- package/test/scripting/txt/decompile.spec.js +0 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botium-core",
|
|
3
|
-
"version": "1.14.
|
|
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",
|
package/src/scripting/Convo.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
86
|
+
this.globalAsserters = {}
|
|
87
87
|
this.logicHooks = {}
|
|
88
|
-
this.
|
|
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
|
|
323
|
-
|
|
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
|
|
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 = [...
|
|
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.
|
|
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.
|
|
509
|
+
this.globalAsserters = logicHookUtils.getGlobalAsserters()
|
|
503
510
|
this.logicHooks = logicHookUtils.logicHooks
|
|
504
|
-
this.
|
|
511
|
+
this.globalLogicHooks = logicHookUtils.getGlobalLogicHooks()
|
|
505
512
|
this.userInputs = logicHookUtils.userInputs
|
|
506
513
|
}
|
|
507
514
|
|
package/src/scripting/helper.js
CHANGED
|
@@ -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.
|
|
27
|
+
this.globalAsserterNames = []
|
|
28
28
|
this.logicHooks = {}
|
|
29
|
-
this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
96
|
-
return this.
|
|
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
|
-
|
|
101
|
-
return this.
|
|
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
|
|
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 =
|
|
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,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()
|