botium-core 1.14.1 → 1.14.4
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/dist/botium-cjs.js +341 -126
- package/dist/botium-cjs.js.map +1 -1
- package/dist/botium-es.js +359 -144
- package/dist/botium-es.js.map +1 -1
- package/package.json +11 -11
- package/src/Capabilities.js +2 -0
- package/src/scripting/BotiumError.js +40 -3
- package/src/scripting/CompilerMarkdown.js +2 -1
- package/src/scripting/CompilerTxt.js +1 -4
- package/src/scripting/Convo.js +113 -19
- package/src/scripting/MatchFunctions.js +30 -8
- package/src/scripting/ScriptingMemory.js +7 -0
- package/src/scripting/ScriptingProvider.js +87 -36
- package/src/scripting/helper.js +3 -2
- package/src/scripting/logichook/LogicHookConsts.js +5 -2
- package/src/scripting/logichook/LogicHookUtils.js +8 -6
- package/src/scripting/logichook/logichooks/ConvoStepParametersLogicHook.js +6 -0
- package/src/scripting/logichook/logichooks/OrderedListToButtonLogicHook.js +37 -0
- package/test/compiler/compilermarkdown.spec.js +3 -3
- package/test/compiler/compilertxt.spec.js +1 -1
- package/test/compiler/convos/txt/convos_emptyrow_just_emptyrow.convo.txt +1 -1
- package/test/convo/fillAndApplyScriptingMemory.spec.js +11 -0
- package/test/logichooks/orderedListToButton.spec.js +35 -0
- package/test/scripting/asserters/convoStepParameters.spec.js +151 -0
- package/test/scripting/asserters/convos/TEXT_GOOD.convo.txt +6 -0
- package/test/scripting/asserters/convos/convo_step_parameter_matchmode_failed.convo.txt +8 -0
- package/test/scripting/asserters/convos/convo_step_parameter_matchmode_failed_wer.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_asserters_all_good.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_asserters_botium_timeout.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_asserters_good.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_asserters_good_global.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_and_asserter.convo.txt +10 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_botium_timeout.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_but_no_button.convo.txt +10 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_good.convo.txt +9 -0
- package/test/scripting/asserters/convos/convo_step_parameter_retry_main_good_begin.convo.txt +11 -0
- package/test/scripting/logichooks/convos/scripting_memory_overwrite_and_check.convo.txt +2 -2
- package/test/scripting/matching/matchingmode.spec.js +4 -1
- package/test/scripting/scriptingProvider.spec.js +38 -12
|
@@ -92,37 +92,37 @@ module.exports = class ScriptingProvider {
|
|
|
92
92
|
|
|
93
93
|
this.scriptingEvents = {
|
|
94
94
|
onConvoBegin: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
95
|
-
return this._createLogicHookPromises({ hookType: 'onConvoBegin', logicHooks: (convo
|
|
95
|
+
return this._createLogicHookPromises({ hookType: 'onConvoBegin', logicHooks: (convo?.beginLogicHook || []), convo, convoStep, scriptingMemory, ...rest })
|
|
96
96
|
},
|
|
97
97
|
onConvoEnd: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
98
|
-
return this._createLogicHookPromises({ hookType: 'onConvoEnd', logicHooks: (convo
|
|
98
|
+
return this._createLogicHookPromises({ hookType: 'onConvoEnd', logicHooks: (convo?.endLogicHook || []), convo, convoStep, scriptingMemory, ...rest })
|
|
99
99
|
},
|
|
100
100
|
onMeStart: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
101
|
-
return this._createLogicHookPromises({ hookType: 'onMeStart', logicHooks: (convoStep
|
|
101
|
+
return this._createLogicHookPromises({ hookType: 'onMeStart', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
102
102
|
},
|
|
103
103
|
onMePrepare: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
104
|
-
return this._createLogicHookPromises({ hookType: 'onMePrepare', logicHooks: (convoStep
|
|
104
|
+
return this._createLogicHookPromises({ hookType: 'onMePrepare', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
105
105
|
},
|
|
106
106
|
onMeEnd: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
107
|
-
return this._createLogicHookPromises({ hookType: 'onMeEnd', logicHooks: (convoStep
|
|
107
|
+
return this._createLogicHookPromises({ hookType: 'onMeEnd', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
108
108
|
},
|
|
109
109
|
onBotStart: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
110
|
-
return this._createLogicHookPromises({ hookType: 'onBotStart', logicHooks: (convoStep
|
|
110
|
+
return this._createLogicHookPromises({ hookType: 'onBotStart', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
111
111
|
},
|
|
112
112
|
onBotPrepare: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
113
|
-
return this._createLogicHookPromises({ hookType: 'onBotPrepare', logicHooks: (convoStep
|
|
113
|
+
return this._createLogicHookPromises({ hookType: 'onBotPrepare', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
114
114
|
},
|
|
115
115
|
onBotEnd: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
116
|
-
return this._createLogicHookPromises({ hookType: 'onBotEnd', logicHooks: (convoStep
|
|
116
|
+
return this._createLogicHookPromises({ hookType: 'onBotEnd', logicHooks: (convoStep?.logicHooks || []), convo, convoStep, scriptingMemory, ...rest })
|
|
117
117
|
},
|
|
118
118
|
assertConvoBegin: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
119
|
-
return this._createAsserterPromises({ asserterType: 'assertConvoBegin', asserters: (convo
|
|
119
|
+
return this._createAsserterPromises({ asserterType: 'assertConvoBegin', asserters: (convo?.beginAsserter || []), convo, convoStep, scriptingMemory, ...rest })
|
|
120
120
|
},
|
|
121
121
|
assertConvoStep: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
122
|
-
return this._createAsserterPromises({ asserterType: 'assertConvoStep', asserters: (convoStep
|
|
122
|
+
return this._createAsserterPromises({ asserterType: 'assertConvoStep', asserters: (convoStep?.asserters || []), convo, convoStep, scriptingMemory, ...rest })
|
|
123
123
|
},
|
|
124
124
|
assertConvoEnd: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
125
|
-
return this._createAsserterPromises({ asserterType: 'assertConvoEnd', asserters: (convo
|
|
125
|
+
return this._createAsserterPromises({ asserterType: 'assertConvoEnd', asserters: (convo?.endAsserter || []), convo, convoStep, scriptingMemory, ...rest })
|
|
126
126
|
},
|
|
127
127
|
setUserInput: ({ convo, convoStep, scriptingMemory, ...rest }) => {
|
|
128
128
|
return this._createUserInputPromises({ convo, convoStep, scriptingMemory, ...rest })
|
|
@@ -130,18 +130,20 @@ module.exports = class ScriptingProvider {
|
|
|
130
130
|
resolveUtterance: ({ utterance, resolveEmptyIfUnknown }) => {
|
|
131
131
|
return this._resolveUtterance({ utterance, resolveEmptyIfUnknown })
|
|
132
132
|
},
|
|
133
|
-
assertBotResponse: (botresponse, tomatch, stepTag, meMsg) => {
|
|
133
|
+
assertBotResponse: (botresponse, tomatch, stepTag, meMsg, convoStepParameters = {}) => {
|
|
134
134
|
if (!_.isArray(tomatch)) {
|
|
135
135
|
tomatch = [tomatch]
|
|
136
136
|
}
|
|
137
137
|
debug(`assertBotResponse ${stepTag} ${meMsg ? `(${meMsg}) ` : ''}BOT: ${botresponse} = ${tomatch} ...`)
|
|
138
|
-
const
|
|
138
|
+
const matchFn = convoStepParameters.matchingMode ? (getMatchFunction(convoStepParameters.matchingMode) || this.matchFn) : this.matchFn
|
|
139
|
+
const found = _.find(tomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS], convoStepParameters))
|
|
139
140
|
const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
|
|
140
141
|
if (_.isNil(found)) {
|
|
141
|
-
|
|
142
|
+
const matchingMode = convoStepParameters.matchingMode || this.caps[Capabilities.SCRIPTING_MATCHING_MODE]
|
|
143
|
+
if (matchingMode === 'wer') {
|
|
142
144
|
const wer = calculateWer(botresponse, tomatch[0])
|
|
143
145
|
const werArgs = this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]
|
|
144
|
-
const threshold = ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
|
|
146
|
+
const threshold = !_.isNil(convoStepParameters.matchingModeWer) ? (convoStepParameters.matchingModeWer / 100) : ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
|
|
145
147
|
const message = `${stepTag}: Word Error Rate (${toPercent(wer)}) higher than accepted (${toPercent(threshold)})`
|
|
146
148
|
throw new BotiumError(
|
|
147
149
|
message,
|
|
@@ -149,7 +151,7 @@ module.exports = class ScriptingProvider {
|
|
|
149
151
|
type: 'asserter',
|
|
150
152
|
source: asserterType,
|
|
151
153
|
params: {
|
|
152
|
-
matchingMode
|
|
154
|
+
matchingMode,
|
|
153
155
|
args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
|
|
154
156
|
},
|
|
155
157
|
context: {
|
|
@@ -191,18 +193,20 @@ module.exports = class ScriptingProvider {
|
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
195
|
},
|
|
194
|
-
assertBotNotResponse: (botresponse, nottomatch, stepTag, meMsg) => {
|
|
196
|
+
assertBotNotResponse: (botresponse, nottomatch, stepTag, meMsg, convoStepParameters = {}) => {
|
|
195
197
|
if (!_.isArray(nottomatch)) {
|
|
196
198
|
nottomatch = [nottomatch]
|
|
197
199
|
}
|
|
198
200
|
debug(`assertBotNotResponse ${stepTag} ${meMsg ? `(${meMsg}) ` : ''}BOT: ${botresponse} != ${nottomatch} ...`)
|
|
199
|
-
const
|
|
201
|
+
const matchFn = convoStepParameters.matchingMode ? (getMatchFunction(convoStepParameters.matchingMode) || this.matchFn) : this.matchFn
|
|
202
|
+
const found = _.find(nottomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS], convoStepParameters))
|
|
200
203
|
const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
|
|
201
204
|
if (!_.isNil(found)) {
|
|
202
|
-
|
|
205
|
+
const matchingMode = convoStepParameters.matchingMode || this.caps[Capabilities.SCRIPTING_MATCHING_MODE]
|
|
206
|
+
if (matchingMode === 'wer') {
|
|
203
207
|
const wer = calculateWer(botresponse, nottomatch[0])
|
|
204
208
|
const werArgs = this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]
|
|
205
|
-
const threshold = ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
|
|
209
|
+
const threshold = !_.isNil(convoStepParameters.matchingModeWer) ? (convoStepParameters.matchingModeWer / 100) : ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
|
|
206
210
|
const message = `${stepTag}: Word Error Rate (${toPercent(wer)}) lower than accepted (${toPercent(threshold)})`
|
|
207
211
|
throw new BotiumError(
|
|
208
212
|
message,
|
|
@@ -210,7 +214,7 @@ module.exports = class ScriptingProvider {
|
|
|
210
214
|
type: 'asserter',
|
|
211
215
|
source: asserterType,
|
|
212
216
|
params: {
|
|
213
|
-
matchingMode
|
|
217
|
+
matchingMode,
|
|
214
218
|
args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
|
|
215
219
|
},
|
|
216
220
|
context: {
|
|
@@ -270,6 +274,22 @@ module.exports = class ScriptingProvider {
|
|
|
270
274
|
assertConvoStep: 'assertNotConvoStep',
|
|
271
275
|
assertConvoEnd: 'assertNotConvoEnd'
|
|
272
276
|
}
|
|
277
|
+
const updateExceptionContext = (promise, asserter) => {
|
|
278
|
+
const updateError = (err) => {
|
|
279
|
+
if (err instanceof BotiumError) {
|
|
280
|
+
if (!err.context) {
|
|
281
|
+
err.context = {}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
err.context.asserter = asserter.name
|
|
285
|
+
|
|
286
|
+
throw err
|
|
287
|
+
} else {
|
|
288
|
+
throw botiumErrorFromErr(_.isString(err) ? err : err.message, err, { asserter: asserter.name })
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return promise.catch(err => updateError(err))
|
|
292
|
+
}
|
|
273
293
|
const callAsserter = (asserterSpec, asserter, params) => {
|
|
274
294
|
if (asserterSpec.not) {
|
|
275
295
|
const notAsserterType = mapNot[asserterType]
|
|
@@ -301,18 +321,35 @@ module.exports = class ScriptingProvider {
|
|
|
301
321
|
|
|
302
322
|
const convoAsserter = asserters
|
|
303
323
|
.filter(a => this.asserters[a.name][asserterType])
|
|
304
|
-
.map(a =>
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
324
|
+
.map(a => ({
|
|
325
|
+
asserter: a,
|
|
326
|
+
promise: callAsserter(a, this.asserters[a.name], {
|
|
327
|
+
convo,
|
|
328
|
+
convoStep,
|
|
329
|
+
scriptingMemory,
|
|
330
|
+
container,
|
|
331
|
+
args: ScriptingMemory.applyToArgs(a.args, scriptingMemory, container.caps, rest.botMsg),
|
|
332
|
+
isGlobal: false,
|
|
333
|
+
...rest
|
|
334
|
+
})
|
|
312
335
|
}))
|
|
336
|
+
.map(({ promise, asserter }) => updateExceptionContext(promise, asserter))
|
|
337
|
+
|
|
313
338
|
const globalAsserter = Object.values(this.globalAsserter)
|
|
314
339
|
.filter(a => a[asserterType])
|
|
315
|
-
.map(a =>
|
|
340
|
+
.map(a => ({
|
|
341
|
+
asserter: a,
|
|
342
|
+
promise: p(this.retryHelperAsserter, () => a[asserterType]({
|
|
343
|
+
convo,
|
|
344
|
+
convoStep,
|
|
345
|
+
scriptingMemory,
|
|
346
|
+
container,
|
|
347
|
+
args: [],
|
|
348
|
+
isGlobal: true,
|
|
349
|
+
...rest
|
|
350
|
+
}))
|
|
351
|
+
}))
|
|
352
|
+
.map(({ promise, asserter }) => updateExceptionContext(promise, asserter))
|
|
316
353
|
|
|
317
354
|
const allPromises = [...convoAsserter, ...globalAsserter]
|
|
318
355
|
if (this.caps[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS]) {
|
|
@@ -335,8 +372,10 @@ module.exports = class ScriptingProvider {
|
|
|
335
372
|
throw Error(`Unknown hookType ${hookType}`)
|
|
336
373
|
}
|
|
337
374
|
|
|
338
|
-
const
|
|
375
|
+
const localHooks = (logicHooks || [])
|
|
339
376
|
.filter(l => this.logicHooks[l.name][hookType])
|
|
377
|
+
|
|
378
|
+
const convoStepPromises = localHooks
|
|
340
379
|
.map(l => p(this.retryHelperLogicHook, () => this.logicHooks[l.name][hookType]({
|
|
341
380
|
convo,
|
|
342
381
|
convoStep,
|
|
@@ -347,17 +386,24 @@ module.exports = class ScriptingProvider {
|
|
|
347
386
|
...rest
|
|
348
387
|
})))
|
|
349
388
|
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
.map(l => p(this.retryHelperLogicHook, () => l[hookType]({ convo, convoStep, scriptingMemory, container, args: [], isGlobal: true, ...rest })))
|
|
389
|
+
const globalHooks = Object.values(this.globalLogicHook).filter(l => l[hookType])
|
|
390
|
+
const globalPromises = globalHooks.map(l => p(this.retryHelperLogicHook, () => l[hookType]({ convo, convoStep, scriptingMemory, container, args: [], isGlobal: true, ...rest })))
|
|
353
391
|
|
|
354
392
|
const allPromises = [...convoStepPromises, ...globalPromises]
|
|
355
|
-
|
|
393
|
+
|
|
394
|
+
if (allPromises.length > 0) {
|
|
395
|
+
return Promise.all(allPromises).then(() => {
|
|
396
|
+
return {
|
|
397
|
+
// just returning some humanreadable
|
|
398
|
+
hooks: [...localHooks, ...globalHooks].map(h => h.name || h.context?.ref || JSON.stringify(h))
|
|
399
|
+
}
|
|
400
|
+
})
|
|
401
|
+
}
|
|
356
402
|
return Promise.resolve(false)
|
|
357
403
|
}
|
|
358
404
|
|
|
359
405
|
_createUserInputPromises ({ convo, convoStep, scriptingMemory, container, ...rest }) {
|
|
360
|
-
const convoStepPromises = (convoStep
|
|
406
|
+
const convoStepPromises = (convoStep?.userInputs || [])
|
|
361
407
|
.filter(ui => this.userInputs[ui.name])
|
|
362
408
|
.map(ui => p(this.retryHelperUserInput, () => this.userInputs[ui.name].setUserInput({
|
|
363
409
|
convo,
|
|
@@ -424,6 +470,11 @@ module.exports = class ScriptingProvider {
|
|
|
424
470
|
}
|
|
425
471
|
}
|
|
426
472
|
|
|
473
|
+
// Livechat, and crawler using logichooks too. So they need script context
|
|
474
|
+
BuildScriptContext () {
|
|
475
|
+
return this._buildScriptContext()
|
|
476
|
+
}
|
|
477
|
+
|
|
427
478
|
Build () {
|
|
428
479
|
const CompilerXlsx = require('./CompilerXlsx')
|
|
429
480
|
this.compilers[Constants.SCRIPTING_FORMAT_XSLX] = new CompilerXlsx(this._buildScriptContext(), this.caps)
|
package/src/scripting/helper.js
CHANGED
|
@@ -4,6 +4,7 @@ const speechScorer = require('word-error-rate')
|
|
|
4
4
|
const debug = require('debug')('botium-core-scripting-helper')
|
|
5
5
|
|
|
6
6
|
const { E_SCRIPTING_MEMORY_COLUMN_MODE } = require('../Enums')
|
|
7
|
+
const WHITE_SPACES_EXCEPT_SPACE_CHAR_AT_THE_END = /[\n\t\r]+$/
|
|
7
8
|
|
|
8
9
|
const normalizeText = (str, doCleanup) => {
|
|
9
10
|
if (str && _.isArray(str)) {
|
|
@@ -247,7 +248,7 @@ const linesToConvoStep = (lines, sender, context, eol, singleLineMode = false) =
|
|
|
247
248
|
if (eol === null) {
|
|
248
249
|
throw new Error('eol cant be null')
|
|
249
250
|
}
|
|
250
|
-
convoStep.messageText = textLines.join(eol).
|
|
251
|
+
convoStep.messageText = textLines.join(eol).replace(WHITE_SPACES_EXCEPT_SPACE_CHAR_AT_THE_END, '')
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
} else {
|
|
@@ -473,7 +474,7 @@ const convoStepToLines = (step) => {
|
|
|
473
474
|
lines.push(logicHook.name + _formatAppendArgs(logicHook.args))
|
|
474
475
|
})
|
|
475
476
|
}
|
|
476
|
-
return lines
|
|
477
|
+
return lines
|
|
477
478
|
}
|
|
478
479
|
|
|
479
480
|
const linesToScriptingMemories = (lines, columnMode = null) => {
|
|
@@ -61,11 +61,14 @@ module.exports = {
|
|
|
61
61
|
{ name: 'CONDITIONAL_STEP_TIME_BASED', className: 'ConditionalTimeBasedLogicHook' },
|
|
62
62
|
{ name: 'CONDITIONAL_STEP_BUSINESS_HOURS', className: 'ConditionalBusinessHoursLogicHook' },
|
|
63
63
|
{ name: 'CONDITIONAL_STEP_CAPABILITY_VALUE_BASED', className: 'ConditionalCapabilityValueBasedLogicHook' },
|
|
64
|
-
{ name: 'CONDITIONAL_STEP_JSON_PATH_BASED', className: 'ConditionalJsonPathBasedLogicHook.js' }
|
|
64
|
+
{ name: 'CONDITIONAL_STEP_JSON_PATH_BASED', className: 'ConditionalJsonPathBasedLogicHook.js' },
|
|
65
|
+
{ name: 'CONVO_STEP_PARAMETERS', className: 'ConvoStepParametersLogicHook.js' },
|
|
66
|
+
{ name: 'ORDERED_LIST_TO_BUTTON', className: 'OrderedListToButtonLogicHook' }
|
|
65
67
|
],
|
|
66
68
|
DEFAULT_USER_INPUTS: [
|
|
67
69
|
{ name: 'BUTTON', className: 'ButtonInput' },
|
|
68
70
|
{ name: 'MEDIA', className: 'MediaInput' },
|
|
69
71
|
{ name: 'FORM', className: 'FormInput' }
|
|
70
|
-
]
|
|
72
|
+
],
|
|
73
|
+
LOGIC_HOOK_EVENTS: ['onConvoBegin', 'onMeStart', 'onMePrepare', 'onMeEnd', 'onBotStart', 'onBotEnd', 'onBotPrepare', 'onConvoEnd']
|
|
71
74
|
}
|
|
@@ -147,19 +147,21 @@ module.exports = class LogicHookUtils {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
const typeAsText = hookType === 'asserter' ? 'Asserter' : hookType === 'logichook' ? 'Logic Hook' : hookType === 'userinput' ? 'User Input' : 'Unknown'
|
|
151
|
+
|
|
150
152
|
if (isClass(src)) {
|
|
151
153
|
try {
|
|
152
154
|
const CheckClass = src
|
|
153
155
|
return new CheckClass({ ref, ...this.buildScriptContext }, this.caps, args)
|
|
154
156
|
} catch (err) {
|
|
155
|
-
throw new Error(
|
|
157
|
+
throw new Error(`${typeAsText} specification ${ref} from class invalid: ${err.message}`)
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
160
|
if (_.isFunction(src)) {
|
|
159
161
|
try {
|
|
160
162
|
return src({ ref, ...this.buildScriptContext }, this.caps, args)
|
|
161
163
|
} catch (err) {
|
|
162
|
-
throw new Error(
|
|
164
|
+
throw new Error(`${typeAsText} specification ${ref} from function invalid: ${err.message}`)
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
if (_.isObject(src) && !_.isString(src)) {
|
|
@@ -177,7 +179,7 @@ module.exports = class LogicHookUtils {
|
|
|
177
179
|
}, {})
|
|
178
180
|
return hookObject
|
|
179
181
|
} catch (err) {
|
|
180
|
-
throw new Error(
|
|
182
|
+
throw new Error(`${typeAsText} specification ${ref} ${hookType} from provided src (${util.inspect(src)}) invalid: ${err.message}`)
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
|
|
@@ -230,7 +232,7 @@ module.exports = class LogicHookUtils {
|
|
|
230
232
|
try {
|
|
231
233
|
return tryLoadFromSource(tryLoadFile, tryLoad.tryLoadAsserterByName)
|
|
232
234
|
} catch (err) {
|
|
233
|
-
loadErr.push(
|
|
235
|
+
loadErr.push(`${typeAsText} specification ${ref} ${hookType} from "${src}" invalid: ${err.message} `)
|
|
234
236
|
}
|
|
235
237
|
}
|
|
236
238
|
}
|
|
@@ -239,13 +241,13 @@ module.exports = class LogicHookUtils {
|
|
|
239
241
|
try {
|
|
240
242
|
return tryLoadFromSource(tryLoad.tryLoadPackageName, tryLoad.tryLoadAsserterByName)
|
|
241
243
|
} catch (err) {
|
|
242
|
-
loadErr.push(
|
|
244
|
+
loadErr.push(`${typeAsText} specification ${ref} ${hookType} from "${src}" invalid: ${err.message} `)
|
|
243
245
|
}
|
|
244
246
|
}
|
|
245
247
|
}
|
|
246
248
|
|
|
247
249
|
loadErr.forEach(debug)
|
|
248
250
|
}
|
|
249
|
-
throw new Error(
|
|
251
|
+
throw new Error(`${typeAsText} specification ${ref} ${hookType} from "${util.inspect(src)}" invalid : no loader available`)
|
|
250
252
|
}
|
|
251
253
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const _ = require('lodash')
|
|
2
|
+
const PATTERN = '^\\s*(\\d+)\\.'
|
|
3
|
+
const debug = require('debug')('botium-core-OrderedListToButtonLogicHook')
|
|
4
|
+
|
|
5
|
+
module.exports = class OrderedListToButtonLogicHook {
|
|
6
|
+
constructor (context, caps = {}, globalArgs = {}) {
|
|
7
|
+
this.context = context
|
|
8
|
+
this.caps = caps
|
|
9
|
+
this.globalArgs = globalArgs
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
onBotPrepare ({ botMsg, args }) {
|
|
13
|
+
const pattern = args?.[0] || this.globalArgs?.pattern || PATTERN
|
|
14
|
+
let regexp
|
|
15
|
+
try {
|
|
16
|
+
regexp = new RegExp(pattern, 'gm')
|
|
17
|
+
} catch (err) {
|
|
18
|
+
throw new Error(`OrderedListToButtonLogicHook: regex is not valid: ${pattern} ${err.messageText}`)
|
|
19
|
+
}
|
|
20
|
+
const buttons = []
|
|
21
|
+
if (botMsg.messageText && _.isString(botMsg.messageText)) {
|
|
22
|
+
const matches = botMsg.messageText.matchAll(regexp)
|
|
23
|
+
|
|
24
|
+
for (const match of matches) {
|
|
25
|
+
if (match && match[1]) {
|
|
26
|
+
buttons.push({ text: match[1], payload: match[1] })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
debug(`ConditionalBusinessHoursLogicHook onBotPrepare, msg has no messageText to check ${JSON.stringify(botMsg)}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (buttons.length) {
|
|
34
|
+
botMsg.buttons = [...(botMsg.buttons || []), ...buttons]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -161,7 +161,7 @@ describe('compiler.compilermarkdown', function () {
|
|
|
161
161
|
const compiler = new Compiler(context, Object.assign({}, DefaultCapabilities, caps))
|
|
162
162
|
|
|
163
163
|
compiler.Compile(scriptBuffer, 'SCRIPTING_TYPE_CONVO')
|
|
164
|
-
assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag
|
|
164
|
+
assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag\n!hello2')
|
|
165
165
|
assert.equal(context.convos[0].conversation[1].not, true)
|
|
166
166
|
})
|
|
167
167
|
})
|
|
@@ -191,7 +191,7 @@ describe('compiler.compilermarkdown', function () {
|
|
|
191
191
|
compiler.Compile(scriptBuffer, 'SCRIPTING_TYPE_CONVO')
|
|
192
192
|
assert.fail('expected error')
|
|
193
193
|
} catch (err) {
|
|
194
|
-
assert.equal(err.message, 'Failed to parse conversation. All element in convo step has to be optional or not optional: ["?hello meat bag
|
|
194
|
+
assert.equal(err.message, 'Failed to parse conversation. All element in convo step has to be optional or not optional: ["?hello meat bag","BUTTONS checkbutton|checkbutton2"]')
|
|
195
195
|
}
|
|
196
196
|
})
|
|
197
197
|
it('should read ?? as ?', async function () {
|
|
@@ -225,7 +225,7 @@ describe('compiler.compilermarkdown', function () {
|
|
|
225
225
|
const compiler = new Compiler(context, Object.assign({}, DefaultCapabilities, caps))
|
|
226
226
|
|
|
227
227
|
compiler.Compile(scriptBuffer, 'SCRIPTING_TYPE_CONVO')
|
|
228
|
-
assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag
|
|
228
|
+
assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag\n?hello2')
|
|
229
229
|
assert.equal(context.convos[0].conversation[1].optional, true)
|
|
230
230
|
})
|
|
231
231
|
})
|
|
@@ -236,7 +236,7 @@ describe('compiler.compilertxt', function () {
|
|
|
236
236
|
assert.equal(convo.conversation.length, 2)
|
|
237
237
|
assert.equal(convo.conversation[0].messageText, '')
|
|
238
238
|
assert.equal(convo.conversation[0].logicHooks.length, 0)
|
|
239
|
-
assert.equal(convo.conversation[1].messageText, 'Hi')
|
|
239
|
+
assert.equal(convo.conversation[1].messageText, 'Hi ')
|
|
240
240
|
})
|
|
241
241
|
it('should read nothing if there is nothing (even no separator)', async function () {
|
|
242
242
|
const scriptBuffer = fs.readFileSync(path.resolve(__dirname, CONVOS_DIR, 'convos_emptyrow_no_separator_row.convo.txt'))
|
|
@@ -817,6 +817,17 @@ describe('convo.fillAndApplyScriptingMemory', function () {
|
|
|
817
817
|
|
|
818
818
|
assert.equal(result, moment().subtract(1, 'month').format('YYYY.MM.DD'))
|
|
819
819
|
})
|
|
820
|
+
it('date_add_dynamic', async function () {
|
|
821
|
+
const result = ScriptingMemory.apply(
|
|
822
|
+
{ caps: CAPS_ENABLE_SCRIPTING_MEMORY },
|
|
823
|
+
{
|
|
824
|
+
days: 2
|
|
825
|
+
},
|
|
826
|
+
'$date_add($days, "day", YYYY.MM.DD)'
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
assert.equal(result, moment().add(2, 'day').format('YYYY.MM.DD'))
|
|
830
|
+
})
|
|
820
831
|
|
|
821
832
|
it('random', async function () {
|
|
822
833
|
const result = ScriptingMemory.apply(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const assert = require('chai').assert
|
|
2
|
+
|
|
3
|
+
const OrderedListToButtonLogicHook = require('../../src/scripting/logichook/logichooks/OrderedListToButtonLogicHook')
|
|
4
|
+
|
|
5
|
+
describe('logichooks.orderedListToButton', function () {
|
|
6
|
+
it('should convert ordered list to buttons', async function () {
|
|
7
|
+
const orderedListToButtonLogicHook = new OrderedListToButtonLogicHook()
|
|
8
|
+
const botMsg = {
|
|
9
|
+
messageText: `0. sometext.
|
|
10
|
+
1.
|
|
11
|
+
2.
|
|
12
|
+
3.sdfdsf
|
|
13
|
+
4. 3 days per week
|
|
14
|
+
5.2.2
|
|
15
|
+
6. 2.dfsdf
|
|
16
|
+
7777.
|
|
17
|
+
88euro
|
|
18
|
+
`,
|
|
19
|
+
|
|
20
|
+
buttons: [{ payload: 'existingButtonPayload', text: 'existingButtonText' }]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
orderedListToButtonLogicHook.onBotPrepare({ botMsg })
|
|
24
|
+
assert.equal(botMsg.buttons?.length, 9)
|
|
25
|
+
assert.deepEqual(botMsg.buttons[0], { payload: 'existingButtonPayload', text: 'existingButtonText' })
|
|
26
|
+
assert.deepEqual(botMsg.buttons[1], { payload: '0', text: '0' })
|
|
27
|
+
assert.deepEqual(botMsg.buttons[2], { payload: '1', text: '1' })
|
|
28
|
+
assert.deepEqual(botMsg.buttons[3], { payload: '2', text: '2' })
|
|
29
|
+
assert.deepEqual(botMsg.buttons[4], { payload: '3', text: '3' })
|
|
30
|
+
assert.deepEqual(botMsg.buttons[5], { payload: '4', text: '4' })
|
|
31
|
+
assert.deepEqual(botMsg.buttons[6], { payload: '5', text: '5' })
|
|
32
|
+
assert.deepEqual(botMsg.buttons[7], { payload: '6', text: '6' })
|
|
33
|
+
assert.deepEqual(botMsg.buttons[8], { payload: '7777', text: '7777' })
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const assert = require('chai').assert
|
|
3
|
+
const BotDriver = require('../../../index').BotDriver
|
|
4
|
+
const Capabilities = require('../../../index').Capabilities
|
|
5
|
+
const debug = require('debug')('botium-test-logichooks-waitforbot')
|
|
6
|
+
const util = require('util')
|
|
7
|
+
|
|
8
|
+
const createEchoConnector = () => ({ queueBotSays, caps }) => {
|
|
9
|
+
return {
|
|
10
|
+
UserSays (msg) {
|
|
11
|
+
const _send = (msg, timeout) => {
|
|
12
|
+
const botMsg = { sender: 'bot', sourceData: msg.sourceData, messageText: `You said ${msg}` }
|
|
13
|
+
if (msg.toLowerCase().indexOf('button') >= 0) {
|
|
14
|
+
botMsg.buttons = [{ text: 'button1' }, { text: 'button2' }]
|
|
15
|
+
}
|
|
16
|
+
if (msg.toLowerCase().indexOf('intent') >= 0) {
|
|
17
|
+
botMsg.nlp = { intent: { name: 'someIntent', confidence: 0.5 } }
|
|
18
|
+
}
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
debug(`${prefix} Connector is sending message ${util.inspect(botMsg)}`)
|
|
21
|
+
return queueBotSays(botMsg)
|
|
22
|
+
}, timeout)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const prefix = `Testcase "${caps[Capabilities.PROJECTNAME]}"`
|
|
26
|
+
debug(`${prefix} Connector got message ${util.inspect(msg)}`)
|
|
27
|
+
if (msg.messageText) {
|
|
28
|
+
msg.messageText.split('\n').forEach((msgPart, i) => {
|
|
29
|
+
if (msgPart) {
|
|
30
|
+
_send(msgPart, i * 50)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
describe('scripting.asserters.convoStepParametersForAssert', function () {
|
|
38
|
+
beforeEach(async function () {
|
|
39
|
+
const myCaps = {
|
|
40
|
+
[Capabilities.PROJECTNAME]: 'asserters.text',
|
|
41
|
+
[Capabilities.CONTAINERMODE]: createEchoConnector(),
|
|
42
|
+
[Capabilities.WAITFORBOTTIMEOUT]: 1000,
|
|
43
|
+
[Capabilities.SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS]: true,
|
|
44
|
+
[Capabilities.SCRIPTING_CONVO_STEP_PARAMETERS]: '{"ignoreNotMatchedBotResponses": {"timeout": 200, "asserters": ["INTENT"]}}'
|
|
45
|
+
}
|
|
46
|
+
const driver = new BotDriver(myCaps)
|
|
47
|
+
this.compiler = driver.BuildCompiler()
|
|
48
|
+
this.container = await driver.Build()
|
|
49
|
+
})
|
|
50
|
+
afterEach(async function () {
|
|
51
|
+
await this.container.Stop()
|
|
52
|
+
await this.container.Clean()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('scripting.asserters.convoStepParametersForAssert.matchmode', function () {
|
|
56
|
+
it('should not accept bad chatbot response on exact match defined on step', async function () {
|
|
57
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_matchmode_failed.convo.txt')
|
|
58
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await this.compiler.convos[0].Run(this.container)
|
|
62
|
+
assert.fail('should have failed')
|
|
63
|
+
} catch (err) {
|
|
64
|
+
assert.isTrue(err.message.indexOf('"You said Hello" expected to match "Hello"') >= 0)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
it('should not accept bad chatbot response on exact match defined on step even with WER asserter', async function () {
|
|
68
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_matchmode_failed_wer.convo.txt')
|
|
69
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await this.compiler.convos[0].Run(this.container)
|
|
73
|
+
assert.fail('should have failed')
|
|
74
|
+
} catch (err) {
|
|
75
|
+
assert.isTrue(err.message.indexOf('Word Error Rate (50%) higher than accepted (25%)') >= 0)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('scripting.asserters.convoStepParametersForAssert.retry', function () {
|
|
81
|
+
it('should retry until succesful main', async function () {
|
|
82
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_main_good.convo.txt')
|
|
83
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
84
|
+
|
|
85
|
+
await this.compiler.convos[0].Run(this.container)
|
|
86
|
+
})
|
|
87
|
+
it('should retry until succesful asserters', async function () {
|
|
88
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_asserters_good.convo.txt')
|
|
89
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
90
|
+
|
|
91
|
+
await this.compiler.convos[0].Run(this.container)
|
|
92
|
+
})
|
|
93
|
+
it('should retry until succesful asserters all', async function () {
|
|
94
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_asserters_all_good.convo.txt')
|
|
95
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
96
|
+
|
|
97
|
+
await this.compiler.convos[0].Run(this.container)
|
|
98
|
+
})
|
|
99
|
+
it('should retry until succesful main, configured in begin', async function () {
|
|
100
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_main_good_begin.convo.txt')
|
|
101
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
102
|
+
|
|
103
|
+
await this.compiler.convos[0].Run(this.container)
|
|
104
|
+
})
|
|
105
|
+
it('should retry until succesful asserters, configured by cap', async function () {
|
|
106
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_asserters_good_global.convo.txt')
|
|
107
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
108
|
+
|
|
109
|
+
await this.compiler.convos[0].Run(this.container)
|
|
110
|
+
})
|
|
111
|
+
it('should retry until timeout main', async function () {
|
|
112
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_main_botium_timeout.convo.txt')
|
|
113
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await this.compiler.convos[0].Run(this.container)
|
|
117
|
+
assert.fail('should have failed')
|
|
118
|
+
} catch (err) {
|
|
119
|
+
assert.isTrue(err.message.indexOf('error waiting for bot - Bot did not respond within 1s') >= 0)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
it('should retry until timeout asserter', async function () {
|
|
123
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_asserters_botium_timeout.convo.txt')
|
|
124
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await this.compiler.convos[0].Run(this.container)
|
|
128
|
+
assert.fail('should have failed')
|
|
129
|
+
} catch (err) {
|
|
130
|
+
assert.isTrue(err.message.indexOf('error waiting for bot - Bot did not respond within 1s') >= 0)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
it('should not retry on not retriable error', async function () {
|
|
134
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_main_but_no_button.convo.txt')
|
|
135
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await this.compiler.convos[0].Run(this.container)
|
|
139
|
+
assert.fail('should have failed')
|
|
140
|
+
} catch (err) {
|
|
141
|
+
assert.isTrue(err.message.indexOf('Expected button(s) with text "some not existing button"') >= 0)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
it('should retry until every retriable is succesful', async function () {
|
|
145
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_retry_main_and_asserter.convo.txt')
|
|
146
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
147
|
+
|
|
148
|
+
await this.compiler.convos[0].Run(this.container)
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
})
|