diff --git a/package-lock.json b/package-lock.json index 70c6ceac..1968b845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "botium-core", - "version": "1.15.14", + "version": "1.15.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "botium-core", - "version": "1.15.14", + "version": "1.15.16", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4123baf2..ea28beca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "botium-core", - "version": "1.15.15", + "version": "1.15.16", "description": "The Selenium for Chatbots", "main": "index.js", "module": "dist/botium-es.js", diff --git a/src/scripting/ScriptingMemory.js b/src/scripting/ScriptingMemory.js index 520fdbc3..4c555078 100644 --- a/src/scripting/ScriptingMemory.js +++ b/src/scripting/ScriptingMemory.js @@ -214,6 +214,56 @@ const SCRIPTING_FUNCTIONS = _.mapValues(SCRIPTING_FUNCTIONS_RAW, (funcOrStruct, }) const RESERVED_WORDS = Object.keys(SCRIPTING_FUNCTIONS) +// #region helper functions +const _convoMemoryPrefix = (convoName) => `$Convo__${convoName}__` + +const _shortNameFromConvoMemory = (convoName, varName) => { + if (!convoName || !_.isString(varName)) { + return null + } + const prefix = _convoMemoryPrefix(convoName) + if (!varName.startsWith(prefix)) { + return null + } + const short = varName.slice(prefix.length) + if (!short) { + return null + } + return `$${short}` +} + +const convoMemoryAliases = (convoName, varNames) => { + const names = varNames || [] + if (!convoName) { + return [...names] + } + const aliased = [] + for (const name of names) { + aliased.push(name) + const short = _shortNameFromConvoMemory(convoName, name) + if (short) { + aliased.push(short) + } + } + return _.uniq(aliased) +} + +const aliasConvoMemory = (convoName, values) => { + const result = Object.assign({}, values || {}) + if (!convoName || !values) { + return result + } + for (const [key, value] of Object.entries(values)) { + const short = _shortNameFromConvoMemory(convoName, key) + if (short) { + result[short] = value + } + } + return result +} +// #endregion + +// #region main functions const apply = (container, scriptingMemory, str, mockMsg) => { if (container.caps[Capabilities.SCRIPTING_ENABLE_MEMORY]) { str = _apply(scriptingMemory, str, container.caps, mockMsg) @@ -328,12 +378,15 @@ const fill = (container, scriptingMemory, result, utterance, scriptingEvents) => debug(`fill end: ${util.inspect(scriptingMemory)}`) } } +// #endregion module.exports = { apply, applyToArgs, fill, extractVarNames, + convoMemoryAliases, + aliasConvoMemory, RESERVED_WORDS, SCRIPTING_FUNCTIONS } diff --git a/src/scripting/ScriptingProvider.js b/src/scripting/ScriptingProvider.js index 40cabf89..1d41d7c7 100644 --- a/src/scripting/ScriptingProvider.js +++ b/src/scripting/ScriptingProvider.js @@ -876,7 +876,8 @@ module.exports = class ScriptingProvider { const multipliers = [] for (const [key, scriptingMemories] of variablesToScriptingMemory.entries()) { const variableNames = JSON.parse(key) - if (_.intersection(variableNames, convoVariables).length) { + const variableNamesForMatch = ScriptingMemory.convoMemoryAliases(convo.header.name, variableNames) + if (_.intersection(variableNamesForMatch, convoVariables).length) { const convosExpandedVariable = [] multipliers.push(scriptingMemories.length) scriptingMemories.forEach((scriptingMemory) => { @@ -884,13 +885,22 @@ module.exports = class ScriptingProvider { for (const convoToExpand of convosToExpand) { const convoExpanded = _.cloneDeep(convoToExpand) convoExpanded.header.name = convoToExpand.header.name + '.' + scriptingMemory.header.name - variableNames.forEach((name) => { - const value = scriptingMemory.values[name] + const appendMemoryHook = (name, value) => { if (value) { - convoExpanded.beginLogicHook.push({ name: 'SET_SCRIPTING_MEMORY', args: [name.substring(1), scriptingMemory.values[name]] }) + convoExpanded.beginLogicHook.push({ name: 'SET_SCRIPTING_MEMORY', args: [name.substring(1), value] }) } else { convoExpanded.beginLogicHook.push({ name: 'CLEAR_SCRIPTING_MEMORY', args: [name.substring(1)] }) } + } + variableNames.forEach((name) => { + appendMemoryHook(name, scriptingMemory.values[name]) + }) + // scoped $Convo____* aliases after originals so they win over a global of the same short name + variableNames.forEach((name) => { + const shortNames = ScriptingMemory.convoMemoryAliases(convo.header.name, [name]).filter(n => n !== name) + shortNames.forEach((shortName) => { + appendMemoryHook(shortName, scriptingMemory.values[name]) + }) }) convosExpandedVariable.push(convoExpanded) } diff --git a/test/scripting/scriptingmemory/convoMemory.spec.js b/test/scripting/scriptingmemory/convoMemory.spec.js new file mode 100644 index 00000000..56339871 --- /dev/null +++ b/test/scripting/scriptingmemory/convoMemory.spec.js @@ -0,0 +1,108 @@ +const path = require('path') +const assert = require('chai').assert +const BotDriver = require('../../..').BotDriver +const Capabilities = require('../../..').Capabilities +const ScriptingMemory = require('../../../src/scripting/ScriptingMemory') + +// #region helper functions +const echoConnector = ({ queueBotSays }) => { + return { + UserSays (msg) { + queueBotSays({ + sender: 'bot', + sourceData: msg.sourceData || { request: msg.messageText }, + messageText: msg.messageText + }) + } + } +} + +const convoByName = (compiler, name) => compiler.convos.find(c => c.header.name === name) + +const meTexts = (transcript) => transcript.steps + .filter(s => s.actual && s.actual.sender !== 'bot') + .map(s => s.actual.messageText) + .filter(t => t) +// #endregion + +describe('scripting.scriptingmemory.convoMemory', function () { + describe('alias helpers', function () { + it('aliasConvoMemory copies scoped keys to short names for the current convo', function () { + const values = { + $Convo__Checkout__orderId: '342', + $Convo__Checkout__username: 'Joe', + $Convo__Payment__username: 'Jane', + $token: 'secret' + } + const aliased = ScriptingMemory.aliasConvoMemory('Checkout', values) + assert.equal(aliased.$orderId, '342') + assert.equal(aliased.$username, 'Joe') + assert.equal(aliased.$Convo__Checkout__orderId, '342') + assert.equal(aliased.$Convo__Payment__username, 'Jane') + assert.isUndefined(aliased.$pw) + assert.equal(aliased.$token, 'secret') + assert.equal(values.$orderId, undefined) + }) + + it('aliasConvoMemory lets scoped names win over a global of the same short name', function () { + const aliased = ScriptingMemory.aliasConvoMemory('Checkout', { + $orderId: 'global', + $Convo__Checkout__orderId: '342' + }) + assert.equal(aliased.$orderId, '342') + }) + + it('convoMemoryAliases adds short names for the current convo prefix only', function () { + const aliased = ScriptingMemory.convoMemoryAliases('Checkout', [ + '$Convo__Checkout__orderId', + '$Convo__Payment__username', + '$newPWD' + ]) + assert.deepEqual(aliased, [ + '$Convo__Checkout__orderId', + '$orderId', + '$Convo__Payment__username', + '$newPWD' + ]) + }) + }) + + describe('expand and run', function () { + beforeEach(async function () { + const driver = new BotDriver({ + [Capabilities.PROJECTNAME]: 'scripting.scriptingmemory.convoMemory', + [Capabilities.CONTAINERMODE]: echoConnector, + [Capabilities.SCRIPTING_ENABLE_MEMORY]: true + }) + this.compiler = driver.BuildCompiler() + this.container = await driver.Build() + }) + + afterEach(async function () { + this.container && await this.container.Clean() + }) + + it('expands Checkout and ChangePassword with case suffix and applies scoped values', async function () { + this.compiler.ReadScriptsFromDirectory(path.resolve(__dirname, 'convosConvoMemory')) + this.compiler.ExpandScriptingMemoryToConvos() + this.compiler.ExpandConvos() + + assert.sameMembers(this.compiler.convos.map(c => c.header.name), ['Checkout.One', 'ChangePassword.One']) + + await this.container.Start() + const checkout = await convoByName(this.compiler, 'Checkout.One').Run(this.container) + await this.container.Stop() + assert.deepEqual(meTexts(checkout), ['login Joe 555', 'my order is 342']) + assert.equal(checkout.scriptingMemory.$username, 'Joe') + assert.equal(checkout.scriptingMemory.$orderId, '342') + assert.equal(checkout.scriptingMemory.$Convo__Payment__username, 'Jane') + + await this.container.Start() + const changePassword = await convoByName(this.compiler, 'ChangePassword.One').Run(this.container) + await this.container.Stop() + assert.deepEqual(meTexts(changePassword), ['login Tom 234', 'change password to 78977']) + assert.equal(changePassword.scriptingMemory.$username, 'Tom') + assert.equal(changePassword.scriptingMemory.$newPWD, '78977') + }) + }) +}) diff --git a/test/scripting/scriptingmemory/convosConvoMemory/cases.scriptingmemory.txt b/test/scripting/scriptingmemory/convosConvoMemory/cases.scriptingmemory.txt new file mode 100644 index 00000000..9107e85e --- /dev/null +++ b/test/scripting/scriptingmemory/convosConvoMemory/cases.scriptingmemory.txt @@ -0,0 +1,2 @@ +Case|$Convo__Payment__username|$Convo__Payment__pw|$Convo__Checkout__username|$Convo__Checkout__pw|$Convo__Checkout__orderId|$Convo__ChangePassword__username|$Convo__ChangePassword__pw|$token|$newPWD +One|Jane|123|Joe|555|342|Tom|234|secret|78977 diff --git a/test/scripting/scriptingmemory/convosConvoMemory/changepassword.convo.txt b/test/scripting/scriptingmemory/convosConvoMemory/changepassword.convo.txt new file mode 100644 index 00000000..7182f475 --- /dev/null +++ b/test/scripting/scriptingmemory/convosConvoMemory/changepassword.convo.txt @@ -0,0 +1,13 @@ +ChangePassword + +#me +login $username $pw + +#bot +login $username $pw + +#me +change password to $newPWD + +#bot +change password to $newPWD diff --git a/test/scripting/scriptingmemory/convosConvoMemory/checkout.convo.txt b/test/scripting/scriptingmemory/convosConvoMemory/checkout.convo.txt new file mode 100644 index 00000000..2fe343ad --- /dev/null +++ b/test/scripting/scriptingmemory/convosConvoMemory/checkout.convo.txt @@ -0,0 +1,9 @@ +Checkout + +#include Login + +#me +my order is $orderId + +#bot +my order is $orderId diff --git a/test/scripting/scriptingmemory/convosConvoMemory/login.pconvo.txt b/test/scripting/scriptingmemory/convosConvoMemory/login.pconvo.txt new file mode 100644 index 00000000..c53edfc3 --- /dev/null +++ b/test/scripting/scriptingmemory/convosConvoMemory/login.pconvo.txt @@ -0,0 +1,7 @@ +Login + +#me +login $username $pw + +#bot +login $username $pw