botium-core 1.13.17 → 1.13.19
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 -1
- package/dist/botium-cjs.js +118 -15
- package/dist/botium-cjs.js.map +1 -1
- package/dist/botium-es.js +118 -15
- package/dist/botium-es.js.map +1 -1
- package/package.json +2 -1
- package/src/BotDriver.js +2 -2
- package/src/Capabilities.js +6 -0
- package/src/containers/BaseContainer.js +7 -4
- package/src/containers/plugins/SimpleRestContainer.js +49 -0
- package/src/scripting/Convo.js +21 -0
- package/src/scripting/ScriptingProvider.js +19 -2
- package/src/scripting/logichook/LogicHookConsts.js +5 -1
- package/src/scripting/logichook/asserter/ButtonsAsserter.js +21 -8
- package/src/scripting/logichook/logichooks/ClearQueueLogicHook.js +0 -1
- package/src/scripting/logichook/logichooks/ConditionalBusinessHoursLogicHook.js +56 -0
- package/src/scripting/logichook/logichooks/ConditionalCapabilityValueBasedLogicHook.js +37 -0
- package/src/scripting/logichook/logichooks/ConditionalJsonPathBasedLogicHook.js +31 -0
- package/src/scripting/logichook/logichooks/ConditionalTimeBasedLogicHook.js +46 -0
- package/test/connectors/logicHook.js +0 -1
- package/test/connectors/simplerest.spec.js +79 -4
- package/test/scripting/asserters/buttonsAsserter.spec.js +84 -50
- package/test/scripting/logichooks/CustomConditionalLogicHook.js +21 -0
- package/test/scripting/logichooks/conditionalStepBusinessHoursLogicHook.spec.js +130 -0
- package/test/scripting/logichooks/conditionalStepCapabilityValueBasedLogicHook.spec.js +35 -0
- package/test/scripting/logichooks/conditionalStepJsonPathBasedLogicHook.spec.js +35 -0
- package/test/scripting/logichooks/conditionalStepTimeBasedLogicHook.spec.js +91 -0
- package/test/scripting/logichooks/convos/conditional_steps.convo.txt +12 -0
- package/test/scripting/logichooks/convos/conditional_steps_business_hours.convo.txt +16 -0
- package/test/scripting/logichooks/convos/conditional_steps_cap_value_based.convo.txt +12 -0
- package/test/scripting/logichooks/convos/conditional_steps_followed_by_bot_msg.convo.txt +15 -0
- package/test/scripting/logichooks/convos/conditional_steps_followed_by_me.convo.txt +18 -0
- package/test/scripting/logichooks/convos/conditional_steps_json_path_based.convo.txt.convo.txt +12 -0
- package/test/scripting/logichooks/convos/conditional_steps_multiple_condition_groups.convo.txt +20 -0
- package/test/scripting/logichooks/convos/conditional_steps_multiple_condition_groups_no_assertion.convo.txt +20 -0
- package/test/scripting/logichooks/convos/conditional_steps_time_based.convo.txt +12 -0
- package/test/scripting/logichooks/customConditionalStepLogicHook.spec.js +105 -0
- package/test/scripting/scriptingProvider.spec.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botium-core",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.19",
|
|
4
4
|
"description": "The Selenium for Chatbots",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "dist/botium-es.js",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"mime-types": "^2.1.35",
|
|
52
52
|
"mkdirp": "^3.0.1",
|
|
53
53
|
"moment": "^2.29.4",
|
|
54
|
+
"moment-timezone": "^0.5.43",
|
|
54
55
|
"mustache": "^4.2.0",
|
|
55
56
|
"promise-retry": "^2.0.1",
|
|
56
57
|
"promise.allsettled": "^1.0.6",
|
package/src/BotDriver.js
CHANGED
|
@@ -3,7 +3,7 @@ const fs = require('fs')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const async = require('async')
|
|
5
5
|
const { rimraf } = require('rimraf')
|
|
6
|
-
const
|
|
6
|
+
const mkdirp = require('mkdirp')
|
|
7
7
|
const sanitize = require('sanitize-filename')
|
|
8
8
|
const moment = require('moment')
|
|
9
9
|
const randomize = require('randomatic')
|
|
@@ -130,7 +130,7 @@ module.exports = class BotDriver {
|
|
|
130
130
|
(tempDirectoryCreated) => {
|
|
131
131
|
tempDirectory = path.resolve(process.cwd(), this.caps[Capabilities.TEMPDIR], sanitize(`${this.caps[Capabilities.PROJECTNAME]} ${moment().format('YYYYMMDD HHmmss')} ${randomize('Aa0', 5)}`))
|
|
132
132
|
try {
|
|
133
|
-
|
|
133
|
+
mkdirp.sync(tempDirectory)
|
|
134
134
|
tempDirectoryCreated()
|
|
135
135
|
} catch (err) {
|
|
136
136
|
tempDirectoryCreated(new Error(`Unable to create temp directory ${tempDirectory}: ${err.message}`))
|
package/src/Capabilities.js
CHANGED
|
@@ -68,6 +68,12 @@ module.exports = {
|
|
|
68
68
|
SIMPLEREST_POLL_INTERVAL: 'SIMPLEREST_POLL_INTERVAL',
|
|
69
69
|
SIMPLEREST_POLL_TIMEOUT: 'SIMPLEREST_PING_TIMEOUT',
|
|
70
70
|
SIMPLEREST_POLL_UPDATE_CONTEXT: 'SIMPLEREST_POLL_UPDATE_CONTEXT',
|
|
71
|
+
SIMPLEREST_CONTEXT_IGNORE_JSONPATH: 'SIMPLEREST_CONTEXT_IGNORE_JSONPATH',
|
|
72
|
+
SIMPLEREST_CONTEXT_IGNORE_MATCH: 'SIMPLEREST_CONTEXT_IGNORE_MATCH',
|
|
73
|
+
SIMPLEREST_CONTEXT_SKIP_JSONPATH: 'SIMPLEREST_CONTEXT_SKIP_JSONPATH',
|
|
74
|
+
SIMPLEREST_CONTEXT_SKIP_MATCH: 'SIMPLEREST_CONTEXT_SKIP_MATCH',
|
|
75
|
+
SIMPLEREST_CONTEXT_CONTINUE_JSONPATH: 'SIMPLEREST_CONTEXT_CONTINUE_JSONPATH',
|
|
76
|
+
SIMPLEREST_CONTEXT_CONTINUE_MATCH: 'SIMPLEREST_CONTEXT_CONTINUE_MATCH',
|
|
71
77
|
SIMPLEREST_BODY_JSONPATH: 'SIMPLEREST_BODY_JSONPATH',
|
|
72
78
|
SIMPLEREST_RESPONSE_JSONPATH: 'SIMPLEREST_RESPONSE_JSONPATH',
|
|
73
79
|
SIMPLEREST_RESPONSE_HOOK: 'SIMPLEREST_RESPONSE_HOOK',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const util = require('util')
|
|
2
2
|
const async = require('async')
|
|
3
|
-
const
|
|
3
|
+
const rimraf = require('rimraf')
|
|
4
4
|
const Bottleneck = require('bottleneck')
|
|
5
5
|
const _ = require('lodash')
|
|
6
6
|
const request = require('request')
|
|
@@ -173,9 +173,12 @@ module.exports = class BaseContainer {
|
|
|
173
173
|
(rimraffed) => {
|
|
174
174
|
if (this.caps[Capabilities.CLEANUPTEMPDIR]) {
|
|
175
175
|
debug(`Cleanup rimrafing temp dir ${this.tempDirectory}`)
|
|
176
|
-
|
|
177
|
-
.
|
|
178
|
-
|
|
176
|
+
try {
|
|
177
|
+
rimraf.sync(this.tempDirectory)
|
|
178
|
+
rimraffed()
|
|
179
|
+
} catch (err) {
|
|
180
|
+
rimraffed(new Error(`Cleanup temp directory ${this.tempDirectory} failed: ${util.inspect(err)}`))
|
|
181
|
+
}
|
|
179
182
|
} else {
|
|
180
183
|
rimraffed()
|
|
181
184
|
}
|
|
@@ -293,6 +293,47 @@ module.exports = class SimpleRestContainer {
|
|
|
293
293
|
debug(`current session context: ${util.inspect(this.view.context)}`)
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
const _isAnyContextJsonPathMatch = (capName, capNameMatch) => {
|
|
297
|
+
const jsonPaths = getAllCapValues(capName, this.caps)
|
|
298
|
+
if (jsonPaths.length > 0) {
|
|
299
|
+
const jsonPathsMatch = getAllCapValues(capNameMatch, this.caps)
|
|
300
|
+
for (const [index, jsonPath] of jsonPaths.entries()) {
|
|
301
|
+
const contextNodes = jp.query(this.view.context, jsonPath)
|
|
302
|
+
if (_.isArray(contextNodes) && contextNodes.length > 0) {
|
|
303
|
+
if (jsonPathsMatch[index]) {
|
|
304
|
+
if (contextNodes[0] === jsonPathsMatch[index]) {
|
|
305
|
+
return {
|
|
306
|
+
jsonPath,
|
|
307
|
+
match: contextNodes[0]
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
return {
|
|
312
|
+
jsonPath
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return null
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const ignoreMatch = _isAnyContextJsonPathMatch(Capabilities.SIMPLEREST_CONTEXT_IGNORE_JSONPATH, Capabilities.SIMPLEREST_CONTEXT_IGNORE_MATCH)
|
|
322
|
+
if (ignoreMatch) {
|
|
323
|
+
if (ignoreMatch.match) debug(`ignoring response for context match: ${ignoreMatch.jsonPath} = ${ignoreMatch.match}`)
|
|
324
|
+
else debug(`ignoring response for context: ${ignoreMatch.jsonPath}`)
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const skipMatch = _isAnyContextJsonPathMatch(Capabilities.SIMPLEREST_CONTEXT_SKIP_JSONPATH, Capabilities.SIMPLEREST_CONTEXT_SKIP_MATCH)
|
|
329
|
+
if (skipMatch) {
|
|
330
|
+
if (skipMatch.match) debug(`skipping response for context match: ${skipMatch.jsonPath} = ${skipMatch.match}`)
|
|
331
|
+
else debug(`skipping response for context: ${skipMatch.jsonPath}`)
|
|
332
|
+
|
|
333
|
+
setTimeout(() => this._doRequest({ messageText: '' }, true, true), 0)
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
296
337
|
const result = []
|
|
297
338
|
if (isFromUser) {
|
|
298
339
|
const _extractFrom = (root, jsonPaths) => {
|
|
@@ -442,6 +483,14 @@ module.exports = class SimpleRestContainer {
|
|
|
442
483
|
}
|
|
443
484
|
}
|
|
444
485
|
}
|
|
486
|
+
|
|
487
|
+
const continueMatch = _isAnyContextJsonPathMatch(Capabilities.SIMPLEREST_CONTEXT_CONTINUE_JSONPATH, Capabilities.SIMPLEREST_CONTEXT_CONTINUE_MATCH)
|
|
488
|
+
if (continueMatch) {
|
|
489
|
+
if (continueMatch.match) debug(`continue with next response for context match: ${continueMatch.jsonPath} = ${continueMatch.match}`)
|
|
490
|
+
else debug(`continue with next response for context: ${continueMatch.jsonPath}`)
|
|
491
|
+
|
|
492
|
+
setTimeout(() => this._doRequest({ messageText: '' }, true, true), 0)
|
|
493
|
+
}
|
|
445
494
|
return result
|
|
446
495
|
}
|
|
447
496
|
|
package/src/scripting/Convo.js
CHANGED
|
@@ -356,6 +356,7 @@ class Convo {
|
|
|
356
356
|
throw failErr
|
|
357
357
|
}
|
|
358
358
|
} else if (convoStep.sender === 'bot') {
|
|
359
|
+
const previousWaitForBotSays = waitForBotSays
|
|
359
360
|
if (waitForBotSays) {
|
|
360
361
|
botMsg = null
|
|
361
362
|
} else {
|
|
@@ -404,6 +405,26 @@ class Convo {
|
|
|
404
405
|
throw failErr
|
|
405
406
|
}
|
|
406
407
|
|
|
408
|
+
if (convoStep.conditional) {
|
|
409
|
+
const nextConvoStep = this.conversation[i + 1]
|
|
410
|
+
|
|
411
|
+
if (!previousWaitForBotSays) {
|
|
412
|
+
skipTranscriptStep = true
|
|
413
|
+
}
|
|
414
|
+
waitForBotSays = false
|
|
415
|
+
if (!nextConvoStep || nextConvoStep.sender !== 'bot' || !nextConvoStep.logicHooks || !nextConvoStep.logicHooks.some(lh => lh.name.toUpperCase().startsWith('CONDITIONAL_STEP'))) {
|
|
416
|
+
waitForBotSays = true
|
|
417
|
+
} else {
|
|
418
|
+
const conditionalLogicHook = convoStep.logicHooks.find(lh => lh.name.startsWith('CONDITIONAL_STEP'))
|
|
419
|
+
const nextConditionalLogicHook = nextConvoStep.logicHooks.find(lh => lh.name.startsWith('CONDITIONAL_STEP'))
|
|
420
|
+
waitForBotSays = conditionalLogicHook.args[1] !== nextConditionalLogicHook.args[1]
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (convoStep.conditional.skip) {
|
|
424
|
+
continue
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
407
428
|
if (!botMsg || (!botMsg.messageText && !botMsg.media && !botMsg.buttons && !botMsg.cards && !botMsg.sourceData && !botMsg.nlp)) {
|
|
408
429
|
const failErr = new BotiumError(`${this.header.name}/${convoStep.stepTag}: bot says nothing`)
|
|
409
430
|
debug(failErr)
|
|
@@ -940,10 +940,16 @@ module.exports = class ScriptingProvider {
|
|
|
940
940
|
convoFilter: null
|
|
941
941
|
}, options)
|
|
942
942
|
const expandedConvos = []
|
|
943
|
+
// The globalContext is going to keep the data even if the Object.assign which happening to create the myContext in _expandConvo function
|
|
944
|
+
const context = {
|
|
945
|
+
globalContext: {
|
|
946
|
+
totalConvoCount: 0
|
|
947
|
+
}
|
|
948
|
+
}
|
|
943
949
|
debug(`ExpandConvos - Using utterances expansion mode: ${this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE]}`)
|
|
944
950
|
this.convos.forEach((convo) => {
|
|
945
951
|
convo.expandPartialConvos()
|
|
946
|
-
for (const expanded of this._expandConvo(convo, options,
|
|
952
|
+
for (const expanded of this._expandConvo(convo, options, context)) {
|
|
947
953
|
expanded.header.assertionCount = this.GetAssertionCount(expanded)
|
|
948
954
|
if (options.justHeader) {
|
|
949
955
|
const ConvoWithOnlyHeader = {
|
|
@@ -959,6 +965,7 @@ module.exports = class ScriptingProvider {
|
|
|
959
965
|
}
|
|
960
966
|
})
|
|
961
967
|
this.convos = expandedConvos
|
|
968
|
+
this.totalConvoCount = context.globalContext.totalConvoCount
|
|
962
969
|
if (!options.justHeader) {
|
|
963
970
|
this._sortConvos()
|
|
964
971
|
} else {
|
|
@@ -971,17 +978,24 @@ module.exports = class ScriptingProvider {
|
|
|
971
978
|
// drop unwanted convos
|
|
972
979
|
convoFilter: null
|
|
973
980
|
}, options)
|
|
981
|
+
// The globalContext is going to keep the data even if the Object.assign which happening to create the myContext in _expandConvo function
|
|
982
|
+
const context = {
|
|
983
|
+
globalContext: {
|
|
984
|
+
totalConvoCount: 0
|
|
985
|
+
}
|
|
986
|
+
}
|
|
974
987
|
debug(`ExpandConvos - Using utterances expansion mode: ${this.caps[Capabilities.SCRIPTING_UTTEXPANSION_MODE]}`)
|
|
975
988
|
// creating a nested generator, calling the other.
|
|
976
989
|
// We hope this.convos does not changes while this iterator is used
|
|
977
990
|
const _convosIterable = function * (options) {
|
|
978
991
|
for (const convo of this.convos) {
|
|
979
992
|
convo.expandPartialConvos()
|
|
980
|
-
yield * this._expandConvo(convo, options,
|
|
993
|
+
yield * this._expandConvo(convo, options, context)
|
|
981
994
|
}
|
|
982
995
|
}.bind(this)
|
|
983
996
|
|
|
984
997
|
this.convosIterable = _convosIterable(options)
|
|
998
|
+
this.totalConvoCount = context.globalContext.totalConvoCount
|
|
985
999
|
}
|
|
986
1000
|
|
|
987
1001
|
/**
|
|
@@ -1173,6 +1187,9 @@ module.exports = class ScriptingProvider {
|
|
|
1173
1187
|
}
|
|
1174
1188
|
} else {
|
|
1175
1189
|
const expanded = Object.assign(_.cloneDeep(currentConvo), { conversation: _.cloneDeep(convoStepsStack) })
|
|
1190
|
+
if (!_.isNil(_.get(context, 'globalContext.totalConvoCount'))) {
|
|
1191
|
+
context.globalContext.totalConvoCount++
|
|
1192
|
+
}
|
|
1176
1193
|
if (!options.convoFilter || options.convoFilter(expanded)) {
|
|
1177
1194
|
yield expanded
|
|
1178
1195
|
}
|
|
@@ -57,7 +57,11 @@ module.exports = {
|
|
|
57
57
|
{ name: 'ASSIGN_SCRIPTING_MEMORY', className: 'AssignScriptingMemoryLogicHook' },
|
|
58
58
|
{ name: 'UPDATE_CUSTOM', className: 'UpdateCustomLogicHook' },
|
|
59
59
|
{ name: 'SKIP_BOT_UNCONSUMED', className: 'ClearQueueLogicHook' },
|
|
60
|
-
{ name: LOGIC_HOOK_INCLUDE, className: 'IncludeLogicHook' }
|
|
60
|
+
{ name: LOGIC_HOOK_INCLUDE, className: 'IncludeLogicHook' },
|
|
61
|
+
{ name: 'CONDITIONAL_STEP_TIME_BASED', className: 'ConditionalTimeBasedLogicHook' },
|
|
62
|
+
{ name: 'CONDITIONAL_STEP_BUSINESS_HOURS', className: 'ConditionalBusinessHoursLogicHook' },
|
|
63
|
+
{ name: 'CONDITIONAL_STEP_CAPABILITY_VALUE_BASED', className: 'ConditionalCapabilityValueBasedLogicHook' },
|
|
64
|
+
{ name: 'CONDITIONAL_STEP_JSON_PATH_BASED', className: 'ConditionalJsonPathBasedLogicHook.js' }
|
|
61
65
|
],
|
|
62
66
|
DEFAULT_USER_INPUTS: [
|
|
63
67
|
{ name: 'BUTTON', className: 'ButtonInput' },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const _ = require('lodash')
|
|
1
2
|
const { SCRIPTING_NORMALIZE_TEXT } = require('../../../Capabilities')
|
|
2
3
|
const { BotiumError } = require('../../BotiumError')
|
|
3
4
|
const { buttonsFromMsg } = require('../helpers')
|
|
@@ -11,17 +12,29 @@ module.exports = class ButtonsAsserter {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
_evalButtons (args, botMsg) {
|
|
14
|
-
const allButtons = buttonsFromMsg(botMsg, true).map(b => b.text).filter(b => b).map(b => normalizeText(b, !!this.caps[SCRIPTING_NORMALIZE_TEXT]))
|
|
15
|
+
const allButtons = buttonsFromMsg(botMsg, true).map(b => ({ text: b.text, payload: b.payload })).filter(b => b).map(b => ({ text: normalizeText(b.text, !!this.caps[SCRIPTING_NORMALIZE_TEXT]), payload: b.payload }))
|
|
15
16
|
if (!args || args.length === 0) {
|
|
16
|
-
return { allButtons, buttonsNotFound: [], buttonsFound: allButtons }
|
|
17
|
+
return { allButtons, buttonsNotFound: [], buttonsFound: allButtons.map(b => b.text) }
|
|
17
18
|
}
|
|
18
19
|
const buttonsNotFound = []
|
|
19
20
|
const buttonsFound = []
|
|
21
|
+
const stringifyPayload = (payload) => {
|
|
22
|
+
if (_.isNil(payload)) {
|
|
23
|
+
return ''
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return JSON.stringify(JSON.parse(payload))
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return JSON.stringify(payload)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
20
31
|
for (let i = 0; i < (args || []).length; i++) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
const matchByText = allButtons.some(b => this.context.Match(b.text, normalizeText(args[i], !!this.caps[SCRIPTING_NORMALIZE_TEXT])))
|
|
33
|
+
const matchByPayload = allButtons.some(b => this.context.Match(stringifyPayload(b.payload), normalizeText(args[i], !!this.caps[SCRIPTING_NORMALIZE_TEXT])))
|
|
34
|
+
if (matchByText || matchByPayload) {
|
|
24
35
|
buttonsFound.push(args[i])
|
|
36
|
+
} else {
|
|
37
|
+
buttonsNotFound.push(args[i])
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
40
|
return { allButtons, buttonsNotFound, buttonsFound }
|
|
@@ -42,7 +55,7 @@ module.exports = class ButtonsAsserter {
|
|
|
42
55
|
cause: {
|
|
43
56
|
not: true,
|
|
44
57
|
expected: args,
|
|
45
|
-
actual: allButtons,
|
|
58
|
+
actual: JSON.stringify(allButtons, null, 2),
|
|
46
59
|
diff: buttonsFound
|
|
47
60
|
}
|
|
48
61
|
}
|
|
@@ -67,7 +80,7 @@ module.exports = class ButtonsAsserter {
|
|
|
67
80
|
cause: {
|
|
68
81
|
not: false,
|
|
69
82
|
expected: args,
|
|
70
|
-
actual: allButtons,
|
|
83
|
+
actual: JSON.stringify(allButtons, null, 2),
|
|
71
84
|
diff: buttonsNotFound
|
|
72
85
|
}
|
|
73
86
|
}
|
|
@@ -85,7 +98,7 @@ module.exports = class ButtonsAsserter {
|
|
|
85
98
|
cause: {
|
|
86
99
|
not: false,
|
|
87
100
|
expected: args,
|
|
88
|
-
actual: allButtons,
|
|
101
|
+
actual: JSON.stringify(allButtons, null, 2),
|
|
89
102
|
diff: buttonsNotFound
|
|
90
103
|
}
|
|
91
104
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const util = require('util')
|
|
2
|
+
const moment = require('moment-timezone')
|
|
3
|
+
const debug = require('debug')('botium-core-ConditionalTimeBasedLogicHook')
|
|
4
|
+
|
|
5
|
+
module.exports = class ConditionalBusinessHoursLogicHook {
|
|
6
|
+
constructor (context, caps, globalArgs) {
|
|
7
|
+
this.context = context
|
|
8
|
+
this.caps = caps
|
|
9
|
+
this.globalArgs = globalArgs
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
_isBetween ({ now, start, end, days, timeZone }) {
|
|
13
|
+
if ((!start || !end) && (!days || days.length === 0)) {
|
|
14
|
+
throw new Error('ConditionalBusinessHoursLogicHook: Either start and end time or days array needs to be specified in params')
|
|
15
|
+
}
|
|
16
|
+
now.tz(timeZone)
|
|
17
|
+
if (days && days.length > 0 && !days.includes(now.format('dddd'))) {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!start && !end && days && days.length > 0 && days.includes(now.format('dddd'))) {
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const momentStartTime = moment(start, 'HH:mm')
|
|
26
|
+
const startTime = now.clone().set({ hour: momentStartTime.hour(), minute: momentStartTime.minute() })
|
|
27
|
+
startTime.tz(timeZone)
|
|
28
|
+
const momentEndTime = moment(end, 'HH:mm')
|
|
29
|
+
const endTime = now.clone().set({ hour: momentEndTime.hour(), minute: momentEndTime.minute() })
|
|
30
|
+
endTime.tz(timeZone)
|
|
31
|
+
if (startTime.isSameOrAfter(endTime)) {
|
|
32
|
+
if (now.isSameOrAfter(startTime)) {
|
|
33
|
+
endTime.add(1, 'days')
|
|
34
|
+
} else {
|
|
35
|
+
startTime.add(-1, 'days')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return now.isBetween(startTime, endTime, 'minutes', '[]')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onBotPrepare ({ convo, convoStep, args }) {
|
|
42
|
+
const conditionGroupId = args[1]
|
|
43
|
+
let params
|
|
44
|
+
try {
|
|
45
|
+
params = JSON.parse(args[0])
|
|
46
|
+
} catch (e) {
|
|
47
|
+
throw new Error(`ConditionalBusinessHoursLogicHook: No parsable JSON object found in params: ${e}`)
|
|
48
|
+
}
|
|
49
|
+
convoStep.conditional = {
|
|
50
|
+
conditionGroupId
|
|
51
|
+
}
|
|
52
|
+
params.now = moment()
|
|
53
|
+
convoStep.conditional.skip = !this._isBetween(params)
|
|
54
|
+
debug(`ConditionalBusinessHoursLogicHook onBotPrepare ${convo.header.name}/${convoStep.stepTag}, args: ${util.inspect(args)}, convoStep.conditional: ${convoStep.conditional}`)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const util = require('util')
|
|
2
|
+
const jp = require('jsonpath')
|
|
3
|
+
const _ = require('lodash')
|
|
4
|
+
const debug = require('debug')('botium-core-ConditionalCapabilityValueBasedLogicHook')
|
|
5
|
+
|
|
6
|
+
module.exports = class ConditionalCapabilityValueBasedLogicHook {
|
|
7
|
+
constructor (context, caps, globalArgs) {
|
|
8
|
+
this.context = context
|
|
9
|
+
this.caps = caps
|
|
10
|
+
this.globalArgs = globalArgs
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
_isCapabilityValueEqual ({ capabilityName, jsonPath, value }) {
|
|
14
|
+
if (jsonPath) {
|
|
15
|
+
const capabilityObject = _.isObject(this.caps[capabilityName]) ? this.caps[capabilityName] : JSON.parse(this.caps[capabilityName])
|
|
16
|
+
const values = jp.query(capabilityObject, jsonPath)
|
|
17
|
+
return !!(values && values.length > 0 && values.includes(value))
|
|
18
|
+
} else {
|
|
19
|
+
return this.caps[capabilityName] === value
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onBotPrepare ({ convo, convoStep, args }) {
|
|
24
|
+
const conditionGroupId = args[1]
|
|
25
|
+
let params
|
|
26
|
+
try {
|
|
27
|
+
params = JSON.parse(args[0])
|
|
28
|
+
} catch (e) {
|
|
29
|
+
throw new Error(`ConditionalCapabilityValueBasedLogicHook: No parsable JSON object found in params: ${e}`)
|
|
30
|
+
}
|
|
31
|
+
convoStep.conditional = {
|
|
32
|
+
conditionGroupId
|
|
33
|
+
}
|
|
34
|
+
convoStep.conditional.skip = !this._isCapabilityValueEqual(params)
|
|
35
|
+
debug(`ConditionalCapabilityValueBasedLogicHook onBotPrepare ${convo.header.name}/${convoStep.stepTag}, args: ${util.inspect(args)}, convoStep.conditional: ${convoStep.conditional}`)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const util = require('util')
|
|
2
|
+
const jp = require('jsonpath')
|
|
3
|
+
const debug = require('debug')('botium-core-ConditionalJsonPathBasedLogicHook')
|
|
4
|
+
|
|
5
|
+
module.exports = class ConditionalJsonPathBasedLogicHook {
|
|
6
|
+
constructor (context, caps, globalArgs) {
|
|
7
|
+
this.context = context
|
|
8
|
+
this.caps = caps
|
|
9
|
+
this.globalArgs = globalArgs
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
onBotPrepare ({ convo, convoStep, args, botMsg }) {
|
|
13
|
+
const conditionGroupId = args[1]
|
|
14
|
+
let params
|
|
15
|
+
try {
|
|
16
|
+
params = JSON.parse(args[0])
|
|
17
|
+
} catch (e) {
|
|
18
|
+
throw new Error(`ConditionalJsonPathBasedLogicHook: No parsable JSON object found in params: ${e}`)
|
|
19
|
+
}
|
|
20
|
+
convoStep.conditional = {
|
|
21
|
+
conditionGroupId
|
|
22
|
+
}
|
|
23
|
+
let skip = true
|
|
24
|
+
if (params.jsonPath) {
|
|
25
|
+
const values = jp.query(botMsg, params.jsonPath)
|
|
26
|
+
skip = !(values && values.length > 0 && values.includes(params.value))
|
|
27
|
+
}
|
|
28
|
+
convoStep.conditional.skip = skip
|
|
29
|
+
debug(`ConditionalJsonPathBasedLogicHook onBotPrepare ${convo.header.name}/${convoStep.stepTag}, args: ${util.inspect(args)}, convoStep.conditional: ${convoStep.conditional}`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const util = require('util')
|
|
2
|
+
const moment = require('moment-timezone')
|
|
3
|
+
const debug = require('debug')('botium-core-ConditionalTimeBasedLogicHook')
|
|
4
|
+
|
|
5
|
+
module.exports = class ConditionalTimeBasedLogicHook {
|
|
6
|
+
constructor (context, caps, globalArgs) {
|
|
7
|
+
this.context = context
|
|
8
|
+
this.caps = caps
|
|
9
|
+
this.globalArgs = globalArgs
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
_isBetween ({ now, start, end, timeZone }) {
|
|
13
|
+
now.tz(timeZone)
|
|
14
|
+
const momentStartTime = moment(start, 'HH:mm')
|
|
15
|
+
const startTime = now.clone().set({ hour: momentStartTime.hour(), minute: momentStartTime.minute() })
|
|
16
|
+
startTime.tz(timeZone)
|
|
17
|
+
const momentEndTime = moment(end, 'HH:mm')
|
|
18
|
+
const endTime = now.clone().set({ hour: momentEndTime.hour(), minute: momentEndTime.minute() })
|
|
19
|
+
endTime.tz(timeZone)
|
|
20
|
+
|
|
21
|
+
if (startTime.isSameOrAfter(endTime)) {
|
|
22
|
+
if (now.isSameOrAfter(startTime)) {
|
|
23
|
+
endTime.add(1, 'days')
|
|
24
|
+
} else {
|
|
25
|
+
startTime.add(-1, 'days')
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return now.isBetween(startTime, endTime, 'minutes', '[]')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
onBotPrepare ({ convo, convoStep, args }) {
|
|
32
|
+
const conditionGroupId = args[1]
|
|
33
|
+
let params
|
|
34
|
+
try {
|
|
35
|
+
params = JSON.parse(args[0])
|
|
36
|
+
} catch (e) {
|
|
37
|
+
throw new Error(`ConditionalTimeBasedLogicHook: No parsable JSON object found in params: ${e}`)
|
|
38
|
+
}
|
|
39
|
+
convoStep.conditional = {
|
|
40
|
+
conditionGroupId
|
|
41
|
+
}
|
|
42
|
+
params.now = moment()
|
|
43
|
+
convoStep.conditional.skip = !this._isBetween(params)
|
|
44
|
+
debug(`ConditionalTimeBasedLogicHook onBotPrepare ${convo.header.name}/${convoStep.stepTag}, args: ${util.inspect(args)}, convoStep.conditional: ${convoStep.conditional}`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -319,7 +319,6 @@ describe('connectors.simplerest', function () {
|
|
|
319
319
|
scope2.persist(false)
|
|
320
320
|
})
|
|
321
321
|
})
|
|
322
|
-
|
|
323
322
|
describe('build', function () {
|
|
324
323
|
it('should build JSON GET url', async function () {
|
|
325
324
|
const myCaps = Object.assign({}, myCapsGet)
|
|
@@ -997,7 +996,6 @@ describe('connectors.simplerest', function () {
|
|
|
997
996
|
assert.deepEqual(values, ['$.1', '$.2', '$.3', '$.4'])
|
|
998
997
|
})
|
|
999
998
|
})
|
|
1000
|
-
|
|
1001
999
|
describe('useresponse', function () {
|
|
1002
1000
|
beforeEach(async function () {
|
|
1003
1001
|
this.init = async (caps) => {
|
|
@@ -1151,7 +1149,6 @@ describe('connectors.simplerest', function () {
|
|
|
1151
1149
|
}
|
|
1152
1150
|
})
|
|
1153
1151
|
})
|
|
1154
|
-
|
|
1155
1152
|
describe('inbound', function () {
|
|
1156
1153
|
it('should accept inbound message with matching jsonpath', async function () {
|
|
1157
1154
|
const myCaps = Object.assign({}, myCapsGet)
|
|
@@ -1249,7 +1246,6 @@ describe('connectors.simplerest', function () {
|
|
|
1249
1246
|
return result
|
|
1250
1247
|
})
|
|
1251
1248
|
})
|
|
1252
|
-
|
|
1253
1249
|
describe('polling', function () {
|
|
1254
1250
|
it('should poll HTTP url', async function () {
|
|
1255
1251
|
const caps = {
|
|
@@ -1313,4 +1309,83 @@ describe('connectors.simplerest', function () {
|
|
|
1313
1309
|
scope.persist(false)
|
|
1314
1310
|
}).timeout(5000)
|
|
1315
1311
|
})
|
|
1312
|
+
describe('flow', function () {
|
|
1313
|
+
it('should ignore matching message', async function () {
|
|
1314
|
+
const caps = {
|
|
1315
|
+
[Capabilities.CONTAINERMODE]: 'simplerest',
|
|
1316
|
+
[Capabilities.WAITFORBOTTIMEOUT]: 1000,
|
|
1317
|
+
[Capabilities.SIMPLEREST_URL]: 'https://mock.com/flowignore',
|
|
1318
|
+
[Capabilities.SIMPLEREST_RESPONSE_JSONPATH]: ['$.text'],
|
|
1319
|
+
[Capabilities.SIMPLEREST_CONTEXT_IGNORE_JSONPATH]: '$.ignore',
|
|
1320
|
+
[Capabilities.SIMPLEREST_CONTEXT_IGNORE_MATCH]: 'Y'
|
|
1321
|
+
}
|
|
1322
|
+
const scope = nock('https://mock.com')
|
|
1323
|
+
.get('/flowignore').reply(200, { text: 'ignore it', ignore: 'Y' })
|
|
1324
|
+
|
|
1325
|
+
const driver = new BotDriver(caps)
|
|
1326
|
+
const container = await driver.Build()
|
|
1327
|
+
await container.Start()
|
|
1328
|
+
|
|
1329
|
+
await container.UserSays({ text: 'hallo' })
|
|
1330
|
+
try {
|
|
1331
|
+
await container.WaitBotSays()
|
|
1332
|
+
assert.fail('expected response to be ignored')
|
|
1333
|
+
} catch (err) {
|
|
1334
|
+
assert.equal(err.message, 'Bot did not respond within 1s')
|
|
1335
|
+
}
|
|
1336
|
+
await container.Stop()
|
|
1337
|
+
await container.Clean()
|
|
1338
|
+
scope.persist(false)
|
|
1339
|
+
})
|
|
1340
|
+
it('should skip matching message', async function () {
|
|
1341
|
+
const caps = {
|
|
1342
|
+
[Capabilities.CONTAINERMODE]: 'simplerest',
|
|
1343
|
+
[Capabilities.SIMPLEREST_URL]: 'https://mock.com/flowskip',
|
|
1344
|
+
[Capabilities.SIMPLEREST_RESPONSE_JSONPATH]: ['$.text'],
|
|
1345
|
+
[Capabilities.SIMPLEREST_CONTEXT_SKIP_JSONPATH]: '$.skip',
|
|
1346
|
+
[Capabilities.SIMPLEREST_CONTEXT_SKIP_MATCH]: 'Y'
|
|
1347
|
+
}
|
|
1348
|
+
const scope = nock('https://mock.com')
|
|
1349
|
+
.get('/flowskip').reply(200, { text: 'skip it', skip: 'Y' })
|
|
1350
|
+
.get('/flowskip').reply(200, { text: 'not skip it', skip: 'N' })
|
|
1351
|
+
|
|
1352
|
+
const driver = new BotDriver(caps)
|
|
1353
|
+
const container = await driver.Build()
|
|
1354
|
+
await container.Start()
|
|
1355
|
+
|
|
1356
|
+
await container.UserSays({ text: 'hello' })
|
|
1357
|
+
const botMsg = await container.WaitBotSays()
|
|
1358
|
+
assert.equal(botMsg.messageText, 'not skip it')
|
|
1359
|
+
|
|
1360
|
+
await container.Stop()
|
|
1361
|
+
await container.Clean()
|
|
1362
|
+
scope.persist(false)
|
|
1363
|
+
})
|
|
1364
|
+
it('should continue on matching message', async function () {
|
|
1365
|
+
const caps = {
|
|
1366
|
+
[Capabilities.CONTAINERMODE]: 'simplerest',
|
|
1367
|
+
[Capabilities.SIMPLEREST_URL]: 'https://mock.com/flowcontinue',
|
|
1368
|
+
[Capabilities.SIMPLEREST_RESPONSE_JSONPATH]: ['$.text'],
|
|
1369
|
+
[Capabilities.SIMPLEREST_CONTEXT_CONTINUE_JSONPATH]: '$.continue',
|
|
1370
|
+
[Capabilities.SIMPLEREST_CONTEXT_CONTINUE_MATCH]: 'Y'
|
|
1371
|
+
}
|
|
1372
|
+
const scope = nock('https://mock.com')
|
|
1373
|
+
.get('/flowcontinue').reply(200, { text: 'continue it', continue: 'Y' })
|
|
1374
|
+
.get('/flowcontinue').reply(200, { text: 'not continue it', continue: 'N' })
|
|
1375
|
+
|
|
1376
|
+
const driver = new BotDriver(caps)
|
|
1377
|
+
const container = await driver.Build()
|
|
1378
|
+
await container.Start()
|
|
1379
|
+
|
|
1380
|
+
await container.UserSays({ text: 'hello' })
|
|
1381
|
+
const botMsg1 = await container.WaitBotSays()
|
|
1382
|
+
assert.equal(botMsg1.messageText, 'continue it')
|
|
1383
|
+
const botMsg2 = await container.WaitBotSays()
|
|
1384
|
+
assert.equal(botMsg2.messageText, 'not continue it')
|
|
1385
|
+
|
|
1386
|
+
await container.Stop()
|
|
1387
|
+
await container.Clean()
|
|
1388
|
+
scope.persist(false)
|
|
1389
|
+
})
|
|
1390
|
+
})
|
|
1316
1391
|
})
|