diff --git a/nodes/core/logic/15-change.js b/nodes/core/logic/15-change.js index d98bf6bde..4a1ce33ec 100644 --- a/nodes/core/logic/15-change.js +++ b/nodes/core/logic/15-change.js @@ -107,10 +107,17 @@ module.exports = function(RED) { } if (rule.tot === "msg") { value = RED.util.getMessageProperty(msg,rule.to); - } else if (rule.tot === 'flow') { - value = node.context().flow.get(rule.to); - } else if (rule.tot === 'global') { - value = node.context().global.get(rule.to); + } else if ((rule.tot === 'flow') || + (rule.tot === 'global')) { + return new Promise((resolve,reject) => { + RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg, (err,value) => { + if (err) { + resolve(undefined); + } else { + resolve(value); + } + }); + }); } else if (rule.tot === 'date') { value = Date.now(); } else if (rule.tot === 'jsonata') { @@ -136,7 +143,8 @@ module.exports = function(RED) { if (rule.fromt === "msg") { resolve(RED.util.getMessageProperty(msg,rule.from)); } else if (rule.fromt === 'flow' || rule.fromt === 'global') { - node.context()[rule.fromt].get(rule.from,(err,fromValue) => { + var contextKey = RED.util.parseContextStore(rule.from); + node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => { if (err) { reject(err); } else { @@ -225,6 +233,7 @@ module.exports = function(RED) { } catch(err) {} return msg; } else if (rule.pt === 'flow' || rule.pt === 'global') { + var contextKey = RED.util.parseContextStore(property); return new Promise((resolve,reject) => { var target = node.context()[rule.pt]; var callback = err => { @@ -235,11 +244,11 @@ module.exports = function(RED) { } } if (rule.t === 'delete') { - target.set(property,undefined,callback); + target.set(contextKey.key,undefined,contextKey.store,callback); } else if (rule.t === 'set') { - target.set(property,value,callback); + target.set(contextKey.key,value,contextKey.store,callback); } else if (rule.t === 'change') { - target.get(property,(err,current) => { + target.get(contextKey.key,contextKey.store,(err,current) => { if (err) { reject(err); return; @@ -248,18 +257,18 @@ module.exports = function(RED) { if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) { // str representation of exact from number/boolean // only replace if they match exactly - target.set(property,value,callback); + target.set(contextKey.key,value,contextKey.store,callback); } else { current = current.replace(fromRE,value); - target.set(property,current,callback); + target.set(contextKey.key,current,contextKey.store,callback); } } else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') { if (current == Number(fromValue)) { - target.set(property,value,callback); + target.set(contextKey.key,value,contextKey.store,callback); } } else if (typeof current === 'boolean' && fromType === 'bool') { if (current.toString() === fromValue) { - target.set(property,value,callback); + target.set(contextKey.key,value,contextKey.store,callback); } } }); diff --git a/red/runtime/util.js b/red/runtime/util.js index b6a5a7e7b..a4cb5d694 100644 --- a/red/runtime/util.js +++ b/red/runtime/util.js @@ -591,5 +591,6 @@ module.exports = { normalisePropertyExpression: normalisePropertyExpression, normaliseNodeTypeName: normaliseNodeTypeName, prepareJSONataExpression: prepareJSONataExpression, - evaluateJSONataExpression: evaluateJSONataExpression + evaluateJSONataExpression: evaluateJSONataExpression, + parseContextStore: parseContextStore }; diff --git a/test/nodes/core/logic/15-change_spec.js b/test/nodes/core/logic/15-change_spec.js index f819c5d0d..35c954809 100644 --- a/test/nodes/core/logic/15-change_spec.js +++ b/test/nodes/core/logic/15-change_spec.js @@ -18,17 +18,28 @@ var should = require("should"); var sinon = require("sinon"); var changeNode = require("../../../../nodes/core/logic/15-change.js"); +var Context = require("../../../../red/runtime/nodes/context"); var helper = require("node-red-node-test-helper"); describe('change Node', function() { beforeEach(function(done) { helper.startServer(done); + Context.init({ + contextStorage: { + memory: { + module: "memory" + } + } + }); + Context.load(); }); afterEach(function(done) { helper.unload(); helper.stopServer(done); + Context.clean({allNodes:{}}); + Context.close(); }); it('should load node with defaults', function(done) { @@ -96,6 +107,27 @@ describe('change Node', function() { }); }); + it('sets the value of persistable global context property', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{ "t":"set","p":"#:(memory)::globalValue","pt":"global","to":"changed","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + changeNode1.context().global.get("globalValue", "meomry", function (err, val) { + val.should.equal("changed"); + done(); + }); + } catch(err) { + done(err); + } + }); + changeNode1.context().global.set("globalValue","changeMe","memory"); + changeNode1.receive({payload:""}); + }); + }); + it('sets the value and type of the message property', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "set", "p": "payload", "pt": "msg", "to": "12345", "tot": "num" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}]; @@ -300,6 +332,26 @@ describe('change Node', function() { }); }); + it('changes the value to persistable flow context property', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":"#:(memory)::flowValue","tot":"flow"}],"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + msg.payload.should.eql("Hello World!"); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.context().flow.set("flowValue","Hello World!","memory",function(err) { + changeNode1.receive({payload:""}); + }); + }); + }); + it('changes the value to global context property', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":"globalValue","tot":"global"}],"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}]; @@ -319,6 +371,26 @@ describe('change Node', function() { }); }); + it('changes the value to persistable global context property', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":"#:(memory)::globalValue","tot":"global"}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + msg.payload.should.eql("Hello World!"); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.context().global.set("globalValue","Hello World!","memory", function (err) { + changeNode1.receive({payload:""}); + }); + }); + }); + it('changes the value to a number', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":"123","tot":"num"}],"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}]; @@ -716,6 +788,26 @@ describe('change Node', function() { }); }); + it('changes the value using persistable flow context property', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"#:(memory)::topic","to":"123","fromt":"flow","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + msg.payload.should.equal("abc123abc"); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.context().flow.set("topic","ABC","memory", function (err) { + changeNode1.receive({payload:"abcABCabc"}); + }); + }); + }); + it('changes the value using global context property', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"topic","to":"123","fromt":"global","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}]; @@ -735,6 +827,26 @@ describe('change Node', function() { }); }); + it('changes the value using persistable global context property', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"#:(memory)::topic","to":"123","fromt":"global","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + msg.payload.should.equal("abc123abc"); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.context().global.set("topic","ABC","memory",function (err) { + changeNode1.receive({payload:"abcABCabc"}); + }); + }); + }); + it('changes the number using global context property', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"topic","to":"ABC","fromt":"global","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}]; @@ -754,6 +866,26 @@ describe('change Node', function() { }); }); + it('changes the number using persistable global context property', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"#:(memory)::topic","to":"ABC","fromt":"global","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + msg.payload.should.equal("ABC"); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.context().global.set("topic",123,"memory",function (err) { + changeNode1.receive({payload:123}); + }); + }); + }); + it('changes the value using number - string payload', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"123","to":"456","fromt":"num","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}]; @@ -845,6 +977,28 @@ describe('change Node', function() { }); }); + it('changes the value of the persistable global context', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "change", "p": "#:(memory)::payload", "pt": "global", "from": "Hello", "fromt": "str", "to": "Goodbye", "tot": "str" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, + {id:"helperNode1", type:"helper", wires:[],"z":"flow"}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + helperNode1.context().global.get("payload","memory", function (err, val) { + val.should.equal("Goodbye World!"); + done(); + }); + } catch(err) { + done(err); + } + }); + changeNode1.context().global.set("payload","Hello World!","memory",function (err) { + changeNode1.receive({payload:""}); + }); + }); + }); + it('changes the value and doesnt change type of the flow context for partial match', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "change", "p": "payload", "pt": "flow", "from": "123", "fromt": "str", "to": "456", "tot": "num" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, {id:"helperNode1", type:"helper", wires:[],"z":"flow"}]; @@ -865,6 +1019,29 @@ describe('change Node', function() { }); }); + it('changes the value and doesnt change type of the persistable flow context for partial match', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "change", "p": "#:(memory)::payload", "pt": "flow", "from": "123", "fromt": "str", "to": "456", "tot": "num" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, + {id:"helperNode1", type:"helper", wires:[],"z":"flow"}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + helperNode1.context().flow.get("payload","memory",function (err, val) { + val.should.equal("Change456Me"); + val.should.be.a.String(); + done(); + }); + } catch(err) { + done(err); + } + }); + changeNode1.context().flow.set("payload","Change123Me","memory",function (err) { + changeNode1.receive({payload:""}); + }); + }); + }); + it('changes the value and type of the flow context if a complete match', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "change", "p": "payload", "pt": "flow", "from": "123", "fromt": "str", "to": "456", "tot": "num" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, {id:"helperNode1", type:"helper", wires:[],"z":"flow"}]; @@ -885,6 +1062,27 @@ describe('change Node', function() { }); }); + it('changes the value and type of the flow context if a complete match', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "change", "p": "#:(memory)::payload", "pt": "flow", "from": "123", "fromt": "str", "to": "456", "tot": "num" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, + {id:"helperNode1", type:"helper", wires:[],"z":"flow"}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + helperNode1.context().flow.get("payload").should.equal(456); + helperNode1.context().flow.get("payload").should.be.a.Number(); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.context().flow.set("payload","123","memory",function (err) { + changeNode1.receive({payload:""}); + }); + }); + }); + it('changes the value using number - number flow context', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "change", "p": "payload", "pt": "flow", "from": "123", "fromt": "num", "to": "abc", "tot": "str" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, {id:"helperNode1", type:"helper", wires:[],"z":"flow"}]; @@ -904,6 +1102,28 @@ describe('change Node', function() { }); }); + it('changes the value using number - number persistable flow context', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "change", "p": "#:(memory)::payload", "pt": "flow", "from": "123", "fromt": "num", "to": "abc", "tot": "str" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, + {id:"helperNode1", type:"helper", wires:[],"z":"flow"}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + helperNode1.context().flow.get("payload","memory",function (err, val) { + val.should.equal("abc"); + done(); + }); + } catch(err) { + done(err); + } + }); + changeNode1.context().flow.set("payload",123,"memory",function (err) { + changeNode1.receive({payload:""}); + }); + }); + }); + it('changes the value using boolean - boolean flow context', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "change", "p": "payload", "pt": "flow", "from": "true", "fromt": "bool", "to": "abc", "tot": "str" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, {id:"helperNode1", type:"helper", wires:[],"z":"flow"}]; @@ -923,6 +1143,28 @@ describe('change Node', function() { }); }); + it('changes the value using boolean - boolean persistable flow context', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "change", "p": "#:(memory)::payload", "pt": "flow", "from": "true", "fromt": "bool", "to": "abc", "tot": "str" }],"reg":false,"name":"changeNode","wires":[["helperNode1"]],"z":"flow"}, + {id:"helperNode1", type:"helper", wires:[],"z":"flow"}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + helperNode1.context().flow.get("payload","memory",function (err, val) { + val.should.equal("abc"); + done(); + }); + } catch(err) { + done(err); + } + }); + changeNode1.context().flow.set("payload",true,function (err) { + changeNode1.receive({payload:""}); + }); + }); + }); + describe('env var', function() { before(function() { process.env.NR_TEST_A = 'foo'; @@ -989,6 +1231,28 @@ describe('change Node', function() { }); }); + it('deletes the value of persistable global context property', function(done) { + var flow = [{"id":"changeNode1","type":"change",rules:[{ "t": "delete", "p": "#:(memory)::globalValue", "pt": "global"}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + changeNode1.context().global.get("globalValue","memory",function(err,val) { + should.equal(undefined); + done(); + }); + } catch(err) { + done(err); + } + }); + changeNode1.context().global.set("globalValue","Hello World!","memory",function (err) { + changeNode1.receive({payload:""}); + }); + }); + }); + it('deletes the value of a multi-level message property', function(done) { var flow = [{"id":"changeNode1","type":"change","action":"delete","property":"foo.bar","from":"","to":"","reg":false,"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}];