botium-core 1.12.4 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSES-3RDPARTY.txt +390 -225
  2. package/dist/botium-cjs.js +298 -161
  3. package/dist/botium-cjs.js.map +1 -1
  4. package/dist/botium-es.js +298 -161
  5. package/dist/botium-es.js.map +1 -1
  6. package/package.json +28 -28
  7. package/src/BotDriver.js +24 -19
  8. package/src/Capabilities.js +8 -0
  9. package/src/Defaults.js +1 -0
  10. package/src/containers/plugins/SimpleRestContainer.js +42 -8
  11. package/src/containers/plugins/index.js +1 -1
  12. package/src/helpers/Utils.js +1 -1
  13. package/src/scripting/CompilerCsv.js +150 -102
  14. package/src/scripting/CompilerXlsx.js +2 -2
  15. package/src/scripting/Convo.js +4 -4
  16. package/src/scripting/ScriptingProvider.js +6 -2
  17. package/src/scripting/helper.js +20 -13
  18. package/src/scripting/logichook/asserter/BaseCountAsserter.js +1 -1
  19. package/src/scripting/logichook/asserter/ButtonsAsserter.js +4 -2
  20. package/src/scripting/logichook/asserter/CardsAsserter.js +4 -2
  21. package/test/compiler/compilercsv.spec.js +363 -12
  22. package/test/compiler/compilertxt.spec.js +13 -0
  23. package/test/compiler/convos/csv/utterances_liveperson.csv +108 -0
  24. package/test/compiler/convos/csv/utterances_multicolumn3col.csv +3 -0
  25. package/test/compiler/convos/csv/utterances_multicolumn5col.csv +3 -0
  26. package/test/compiler/convos/csv/utterances_singlecolumn.csv +3 -0
  27. package/test/compiler/convos/csv/utterances_variable_row_len.csv +3 -0
  28. package/test/compiler/convos/txt/convos_args_escape.convo.txt +2 -0
  29. package/test/connectors/simplerest.spec.js +49 -8
  30. package/test/convo/convos/continuefailing_timeout.convo.txt +16 -0
  31. package/test/convo/transcript.spec.js +18 -1
  32. package/test/logichooks/convos/WAITFORBOT_INFINITE.convo.txt +1 -1
  33. package/test/scripting/asserters/buttonsAsserter.spec.js +47 -0
  34. package/test/scripting/asserters/cardsAsserter.spec.js +39 -0
  35. package/test/scripting/scriptingProvider.spec.js +1 -1
  36. package/test/scripting/txt/decompile.spec.js +24 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botium-core",
3
- "version": "1.12.4",
3
+ "version": "1.13.0",
4
4
  "description": "The Selenium for Chatbots",
5
5
  "main": "index.js",
6
6
  "module": "dist/botium-es.js",
@@ -32,25 +32,25 @@
32
32
  },
33
33
  "homepage": "https://www.botium.ai",
34
34
  "dependencies": {
35
- "@babel/runtime": "^7.17.8",
36
- "async": "^3.2.3",
37
- "body-parser": "^1.19.2",
35
+ "@babel/runtime": "^7.18.3",
36
+ "async": "^3.2.4",
37
+ "body-parser": "^1.20.0",
38
38
  "boolean": "^3.2.0",
39
39
  "bottleneck": "^2.19.5",
40
- "csv-parse": "^5.0.4",
40
+ "csv-parse": "^5.2.0",
41
41
  "debug": "^4.3.4",
42
42
  "esprima": "^4.0.1",
43
- "express": "^4.17.3",
43
+ "express": "^4.18.1",
44
44
  "globby": "11.0.4",
45
- "ioredis": "^4.28.5",
45
+ "ioredis": "^5.0.6",
46
46
  "is-class": "^0.0.9",
47
47
  "is-json": "^2.0.1",
48
48
  "jsonpath": "^1.1.1",
49
49
  "lodash": "^4.17.21",
50
- "markdown-it": "^12.3.2",
50
+ "markdown-it": "^13.0.1",
51
51
  "mime-types": "^2.1.35",
52
52
  "mkdirp": "^1.0.4",
53
- "moment": "^2.29.1",
53
+ "moment": "^2.29.3",
54
54
  "mustache": "^4.2.0",
55
55
  "promise-retry": "^2.0.1",
56
56
  "promise.allsettled": "^1.0.5",
@@ -59,39 +59,39 @@
59
59
  "rimraf": "^3.0.2",
60
60
  "sanitize-filename": "^1.6.3",
61
61
  "slugify": "^1.6.5",
62
- "socket.io": "^4.4.1",
63
- "socket.io-client": "^4.4.1",
62
+ "socket.io": "^4.5.1",
63
+ "socket.io-client": "^4.5.1",
64
64
  "socketio-auth": "^0.1.1",
65
- "swagger-jsdoc": "^6.1.0",
66
- "swagger-ui-express": "^4.3.0",
65
+ "swagger-jsdoc": "^6.2.1",
66
+ "swagger-ui-express": "^4.4.0",
67
67
  "uuid": "^8.3.2",
68
68
  "vm2": "^3.9.9",
69
69
  "write-yaml": "^1.0.0",
70
- "xlsx": "^0.18.4",
71
- "xregexp": "^5.1.0",
72
- "yaml": "^1.10.2"
70
+ "xlsx": "^0.18.5",
71
+ "xregexp": "^5.1.1",
72
+ "yaml": "^2.1.1"
73
73
  },
74
74
  "devDependencies": {
75
- "@babel/core": "^7.17.8",
76
- "@babel/node": "^7.16.8",
77
- "@babel/plugin-transform-runtime": "^7.17.0",
78
- "@babel/preset-env": "^7.16.11",
75
+ "@babel/core": "^7.18.5",
76
+ "@babel/node": "^7.18.5",
77
+ "@babel/plugin-transform-runtime": "^7.18.5",
78
+ "@babel/preset-env": "^7.18.2",
79
79
  "chai": "^4.3.6",
80
80
  "chai-as-promised": "^7.1.1",
81
81
  "cross-env": "^7.0.3",
82
- "eslint": "^8.11.0",
83
- "eslint-config-standard": "^16.0.3",
84
- "eslint-plugin-import": "^2.25.4",
85
- "eslint-plugin-node": "^11.1.0",
82
+ "eslint": "^8.18.0",
83
+ "eslint-config-standard": "^17.0.0",
84
+ "eslint-plugin-import": "^2.26.0",
85
+ "eslint-plugin-n": "^15.2.3",
86
86
  "eslint-plugin-promise": "^6.0.0",
87
87
  "eslint-plugin-standard": "^4.1.0",
88
88
  "license-checker": "^25.0.1",
89
89
  "license-compatibility-checker": "^0.3.5",
90
- "mocha": "^9.2.2",
91
- "nock": "^13.2.4",
92
- "npm-check-updates": "^12.5.4",
90
+ "mocha": "^10.0.0",
91
+ "nock": "^13.2.7",
92
+ "npm-check-updates": "^14.0.1",
93
93
  "nyc": "^15.1.0",
94
- "rollup": "^2.70.1",
94
+ "rollup": "^2.75.6",
95
95
  "rollup-plugin-babel": "^4.4.0",
96
96
  "rollup-plugin-commonjs": "^10.1.0",
97
97
  "rollup-plugin-json": "^4.0.0",
package/src/BotDriver.js CHANGED
@@ -115,6 +115,7 @@ module.exports = class BotDriver {
115
115
  this.eventEmitter.emit(Events.CONTAINER_BUILDING)
116
116
 
117
117
  return new Promise((resolve, reject) => {
118
+ let tempDirectory = null
118
119
  let repo = null
119
120
  let container = null
120
121
 
@@ -126,9 +127,19 @@ module.exports = class BotDriver {
126
127
  .catch(driverValidated)
127
128
  },
128
129
 
130
+ (tempDirectoryCreated) => {
131
+ tempDirectory = path.resolve(process.cwd(), this.caps[Capabilities.TEMPDIR], sanitize(`${this.caps[Capabilities.PROJECTNAME]} ${moment().format('YYYYMMDD HHmmss')} ${randomize('Aa0', 5)}`))
132
+ try {
133
+ mkdirp.sync(tempDirectory)
134
+ tempDirectoryCreated()
135
+ } catch (err) {
136
+ tempDirectoryCreated(new Error(`Unable to create temp directory ${tempDirectory}: ${err.message}`))
137
+ }
138
+ },
139
+
129
140
  (repoValidated) => {
130
141
  try {
131
- repo = this._getRepo()
142
+ repo = this._getRepo(tempDirectory)
132
143
  } catch (err) {
133
144
  return repoValidated(err)
134
145
  }
@@ -141,7 +152,7 @@ module.exports = class BotDriver {
141
152
 
142
153
  (containerValidated) => {
143
154
  try {
144
- container = this._getContainer(repo)
155
+ container = this._getContainer(tempDirectory, repo)
145
156
  } catch (err) {
146
157
  return containerValidated(err)
147
158
  }
@@ -156,9 +167,9 @@ module.exports = class BotDriver {
156
167
  if (err) {
157
168
  debug(`BotDriver Build error: ${err}`)
158
169
  this.eventEmitter.emit(Events.CONTAINER_BUILD_ERROR, err)
159
- if (this.tempDirectory) {
160
- rimraf(this.tempDirectory, (err) => {
161
- if (err) debug(`Cleanup temp dir ${this.tempDirectory} failed: ${util.inspect(err)}`)
170
+ if (tempDirectory) {
171
+ rimraf(tempDirectory, (err) => {
172
+ if (err) debug(`Cleanup temp dir ${tempDirectory} failed: ${util.inspect(err)}`)
162
173
  })
163
174
  }
164
175
  return reject(err)
@@ -311,12 +322,6 @@ module.exports = class BotDriver {
311
322
  throw new Error(`Capability '${Capabilities.CONTAINERMODE}' or '${Capabilities.BOTIUMGRIDURL}' missing`)
312
323
  }
313
324
 
314
- this.tempDirectory = path.resolve(process.cwd(), this.caps[Capabilities.TEMPDIR], sanitize(`${this.caps[Capabilities.PROJECTNAME]} ${moment().format('YYYYMMDD HHmmss')} ${randomize('Aa0', 5)}`))
315
- try {
316
- mkdirp.sync(this.tempDirectory)
317
- } catch (err) {
318
- throw new Error(`Unable to create temp directory ${this.tempDirectory}: ${err}`)
319
- }
320
325
  resolve(this)
321
326
  } catch (err) {
322
327
  reject(err)
@@ -324,35 +329,35 @@ module.exports = class BotDriver {
324
329
  })
325
330
  }
326
331
 
327
- _getRepo () {
332
+ _getRepo (tempDirectory) {
328
333
  if (this.caps[Capabilities.BOTIUMGRIDURL]) {
329
334
  const NoRepo = require('./repos/NoRepo')
330
- return new NoRepo(this.tempDirectory, this.sources)
335
+ return new NoRepo(tempDirectory, this.sources)
331
336
  }
332
337
  if (this.sources[Source.GITURL]) {
333
338
  const GitRepo = require('./repos/GitRepo')
334
- return new GitRepo(this.tempDirectory, this.sources)
339
+ return new GitRepo(tempDirectory, this.sources)
335
340
  }
336
341
  if (this.sources[Source.LOCALPATH]) {
337
342
  const LocalRepo = require('./repos/LocalRepo')
338
- return new LocalRepo(this.tempDirectory, this.sources)
343
+ return new LocalRepo(tempDirectory, this.sources)
339
344
  }
340
345
  throw new Error(`No Repo provider found for Sources ${util.inspect(this.sources)}`)
341
346
  }
342
347
 
343
- _getContainer (repo) {
348
+ _getContainer (tempDirectory, repo) {
344
349
  if (this.caps[Capabilities.BOTIUMGRIDURL]) {
345
350
  const GridContainer = require('./containers/GridContainer')
346
- return new GridContainer(this.eventEmitter, this.tempDirectory, repo, this.caps, this.envs)
351
+ return new GridContainer(this.eventEmitter, tempDirectory, repo, this.caps, this.envs)
347
352
  }
348
353
  if (!this.caps[Capabilities.CONTAINERMODE]) {
349
354
  throw new Error(`Capability '${Capabilities.CONTAINERMODE}' missing`)
350
355
  }
351
356
  if (this.caps[Capabilities.CONTAINERMODE] === 'inprocess') {
352
357
  const InProcessContainer = require('./containers/InProcessContainer')
353
- return new InProcessContainer(this.eventEmitter, this.tempDirectory, repo, this.caps, this.envs)
358
+ return new InProcessContainer(this.eventEmitter, tempDirectory, repo, this.caps, this.envs)
354
359
  }
355
360
  const PluginConnectorContainer = require('./containers/PluginConnectorContainer')
356
- return new PluginConnectorContainer(this.eventEmitter, this.tempDirectory, repo, this.caps, this.envs)
361
+ return new PluginConnectorContainer(this.eventEmitter, tempDirectory, repo, this.caps, this.envs)
357
362
  }
358
363
  }
@@ -91,6 +91,7 @@ module.exports = {
91
91
  SIMPLEREST_REDIS_TOPIC: 'SIMPLEREST_REDIS_TOPIC',
92
92
  SIMPLEREST_INBOUND_ORDER_UNSETTLED_EVENTS_JSONPATH: 'SIMPLEREST_INBOUND_ORDER_UNSETTLED_EVENTS_JSONPATH',
93
93
  SIMPLEREST_INBOUND_DEBOUNCE_TIMEOUT: 'SIMPLEREST_INBOUND_DEBOUNCE_TIMEOUT',
94
+ SIMPLEREST_COOKIE_REPLICATION: 'SIMPLEREST_COOKIE_REPLICATION',
94
95
  // Script Compiler
95
96
  SCRIPTING_TXT_EOL: 'SCRIPTING_TXT_EOL',
96
97
  // ROW_PER_MESSAGE or QUESTION_ANSWER
@@ -104,6 +105,10 @@ module.exports = {
104
105
  SCRIPTING_XLSX_SHEETNAMES_PCONVOS: 'SCRIPTING_XLSX_SHEETNAMES_PCONVOS',
105
106
  SCRIPTING_XLSX_SHEETNAMES_UTTERANCES: 'SCRIPTING_XLSX_SHEETNAMES_UTTERANCES',
106
107
  SCRIPTING_XLSX_SHEETNAMES_SCRIPTING_MEMORY: 'SCRIPTING_XLSX_SHEETNAMES_SCRIPTING_MEMORY',
108
+ // hidden capability. All newly in Box created testsets will have this as true. CsvCompiler
109
+ // - throws less error (Box reads csv files as utterances, and convo. Compiler cant throw exception if a file is correct, but box tries to load it with incorrect script type)
110
+ // 4 or more colums are compiled just as utterances.
111
+ SCRIPTING_CSV_LEGACY_MODE_OFF: 'SCRIPTING_CSV_LEGACY_MODE_OFF',
107
112
  SCRIPTING_CSV_DELIMITER: 'SCRIPTING_CSV_DELIMITER',
108
113
  SCRIPTING_CSV_SKIP_HEADER: 'SCRIPTING_CSV_SKIP_HEADER',
109
114
  SCRIPTING_CSV_QUOTE: 'SCRIPTING_CSV_QUOTE',
@@ -113,6 +118,9 @@ module.exports = {
113
118
  SCRIPTING_CSV_MULTIROW_COLUMN_TEXT: 'SCRIPTING_CSV_MULTIROW_COLUMN_TEXT',
114
119
  SCRIPTING_CSV_QA_COLUMN_QUESTION: 'SCRIPTING_CSV_QA_COLUMN_QUESTION',
115
120
  SCRIPTING_CSV_QA_COLUMN_ANSWER: 'SCRIPTING_CSV_QA_COLUMN_ANSWER',
121
+ SCRIPTING_CSV_UTTERANCE_STARTROW: 'SCRIPTING_CSV_UTTERANCE_STARTROW',
122
+ SCRIPTING_CSV_UTTERANCE_STARTROW_HEADER: 'SCRIPTING_CSV_UTTERANCE_STARTROW_HEADER',
123
+ SCRIPTING_CSV_UTTERANCE_STOP_ON_EMPTY: 'SCRIPTING_CSV_UTTERANCE_STOP_ON_EMPTY',
116
124
  SCRIPTING_NORMALIZE_TEXT: 'SCRIPTING_NORMALIZE_TEXT',
117
125
  SCRIPTING_ENABLE_MEMORY: 'SCRIPTING_ENABLE_MEMORY',
118
126
  SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS: 'SCRIPTING_ENABLE_MULTIPLE_ASSERT_ERRORS',
package/src/Defaults.js CHANGED
@@ -34,6 +34,7 @@ module.exports = {
34
34
  [Capabilities.SIMPLEREST_STRICT_SSL]: true,
35
35
  [Capabilities.SIMPLEREST_INBOUND_UPDATE_CONTEXT]: true,
36
36
  [Capabilities.SIMPLEREST_CONTEXT_MERGE_OR_REPLACE]: 'MERGE',
37
+ [Capabilities.SIMPLEREST_COOKIE_REPLICATION]: true,
37
38
  [Capabilities.SCRIPTING_TXT_EOL]: '\n',
38
39
  [Capabilities.SCRIPTING_XLSX_EOL_WRITE]: '\r\n',
39
40
  [Capabilities.SCRIPTING_XLSX_HASHEADERS]: true,
@@ -28,6 +28,7 @@ module.exports = class SimpleRestContainer {
28
28
  this.bottleneck = bottleneck || ((fn) => fn())
29
29
  this.processInbound = false
30
30
  this.redisTopic = this.caps[Capabilities.SIMPLEREST_REDIS_TOPIC] || 'SIMPLEREST_INBOUND_SUBSCRIPTION'
31
+ this.cookies = {}
31
32
 
32
33
  if (this.caps[Capabilities.SIMPLEREST_INBOUND_ORDER_UNSETTLED_EVENTS_JSONPATH]) {
33
34
  const debounceTimeout = this.caps[Capabilities.SIMPLEREST_INBOUND_DEBOUNCE_TIMEOUT] || 500
@@ -451,7 +452,7 @@ module.exports = class SimpleRestContainer {
451
452
 
452
453
  if (body) {
453
454
  debug(`got response code: ${response.statusCode}, body: ${botiumUtils.shortenJsonString(body)}`)
454
-
455
+ this._storeCookiesFromResponse(response)
455
456
  try {
456
457
  body = await this._parseResponseBody(body)
457
458
  } catch (err) {
@@ -560,8 +561,8 @@ module.exports = class SimpleRestContainer {
560
561
  }
561
562
  }
562
563
  this._addRequestOptions(requestOptions)
563
-
564
564
  await executeHook(this.caps, this.requestHook, Object.assign({ requestOptions }, this.view))
565
+ this._addRequestCookies(requestOptions)
565
566
 
566
567
  return requestOptions
567
568
  }
@@ -593,6 +594,7 @@ module.exports = class SimpleRestContainer {
593
594
  await timeout(pingConfig.timeout)
594
595
  } else {
595
596
  debug(`_waitForUrlResponse success on url check ${pingConfig.uri}: ${response.statusCode}/${response.statusMessage}`)
597
+ this._storeCookiesFromResponse(response)
596
598
  if (debug.enabled && body) {
597
599
  debug(botiumUtils.shortenJsonString(body))
598
600
  }
@@ -753,9 +755,9 @@ module.exports = class SimpleRestContainer {
753
755
  const timeout = this._getCapValue(Capabilities.SIMPLEREST_POLL_TIMEOUT)
754
756
  const pollConfig = {
755
757
  method: verb,
756
- uri: uri,
758
+ uri,
757
759
  followAllRedirects: true,
758
- timeout: timeout
760
+ timeout
759
761
  }
760
762
  if (this.caps[Capabilities.SIMPLEREST_POLL_HEADERS]) {
761
763
  try {
@@ -783,6 +785,8 @@ module.exports = class SimpleRestContainer {
783
785
  debug(`_runPolling: exeucting request hook failed - (${err.message})`)
784
786
  return
785
787
  }
788
+ this._addRequestCookies(pollConfig)
789
+
786
790
  request(pollConfig, async (err, response, body) => {
787
791
  if (err) {
788
792
  debug(`_runPolling: rest request failed: ${err.message}, request: ${JSON.stringify(pollConfig)}`)
@@ -794,7 +798,7 @@ module.exports = class SimpleRestContainer {
794
798
  }
795
799
  } else if (body) {
796
800
  debug(`_runPolling: got response code: ${response.statusCode}, body: ${botiumUtils.shortenJsonString(body)}`)
797
-
801
+ this._storeCookiesFromResponse(response)
798
802
  try {
799
803
  body = await this._parseResponseBody(body)
800
804
  } catch (err) {
@@ -835,9 +839,9 @@ module.exports = class SimpleRestContainer {
835
839
  const timeout = this._getCapValue(`${capPrefix}_TIMEOUT`) || this._getCapValue(Capabilities.SIMPLEREST_TIMEOUT)
836
840
  const httpConfig = {
837
841
  method: verb,
838
- uri: uri,
842
+ uri,
839
843
  followAllRedirects: true,
840
- timeout: timeout
844
+ timeout
841
845
  }
842
846
  if (this.caps[`${capPrefix}_HEADERS`]) {
843
847
  try {
@@ -856,8 +860,8 @@ module.exports = class SimpleRestContainer {
856
860
  }
857
861
  }
858
862
  this._addRequestOptions(httpConfig)
859
-
860
863
  await executeHook(this.caps, this.requestHooks[capPrefix], Object.assign({ requestOptions: httpConfig }, this.view))
864
+ this._addRequestCookies(httpConfig)
861
865
 
862
866
  const retries = this._getCapValue(`${capPrefix}_RETRIES`)
863
867
  debug(`_makeCall(${capPrefix}): rest request: ${JSON.stringify(httpConfig)}`)
@@ -874,4 +878,34 @@ module.exports = class SimpleRestContainer {
874
878
  _.merge(httpConfig, this.caps[Capabilities.SIMPLEREST_EXTRA_OPTIONS])
875
879
  }
876
880
  }
881
+
882
+ _addRequestCookies (requestOptions) {
883
+ if (!this.caps[Capabilities.SIMPLEREST_COOKIE_REPLICATION] || !requestOptions) {
884
+ return
885
+ }
886
+ const url = new URL(requestOptions.uri)
887
+ if (!requestOptions.headers) {
888
+ requestOptions.headers = {}
889
+ }
890
+ requestOptions.headers.Cookie = requestOptions.headers.Cookie ? `${requestOptions.headers.Cookie}; ${this.cookies[url.host]}` : this.cookies[url.host]
891
+ }
892
+
893
+ _storeCookiesFromResponse (response) {
894
+ if (!this.caps[Capabilities.SIMPLEREST_COOKIE_REPLICATION] || !response) {
895
+ return
896
+ }
897
+ const responseCookies = response.headers['set-cookie']
898
+ if (!responseCookies) {
899
+ return
900
+ }
901
+ const host = _.get(response, 'request.uri.host')
902
+ let cookie
903
+ responseCookies.forEach(cookieString => {
904
+ cookie = cookie ? `${cookie}; ${cookieString}` : cookieString
905
+ })
906
+
907
+ if (cookie) {
908
+ this.cookies[host] = cookie
909
+ }
910
+ }
877
911
  }
@@ -69,7 +69,7 @@ const tryLoadPlugin = (containermode, modulepath, args) => {
69
69
  source: 'src/containers/plugins/index.js',
70
70
  cause: {
71
71
  SECURITY_ALLOW_UNSAFE: caps[Capabilities.SECURITY_ALLOW_UNSAFE],
72
- mode: mode,
72
+ mode,
73
73
  ...cause
74
74
  }
75
75
  }
@@ -46,7 +46,7 @@ const toJsonWeak = (stringOrNot) => {
46
46
 
47
47
  const optionalJson = (json) => {
48
48
  const body = isJson(json)
49
- return body ? { 'content-type': 'application/json', body: body } : { 'content-type': 'text/plain', body: json }
49
+ return body ? { 'content-type': 'application/json', body } : { 'content-type': 'text/plain', body: json }
50
50
  }
51
51
 
52
52
  const shortenJsonString = (obj) => {