Allow nested msg properties in msg/flow/global expressions (#2822)

* Allow nested msg properties in msg/flow/global expressions

* Remove typo in RED.utils

Co-authored-by: Nick O'Leary <knolleary@users.noreply.github.com>
This commit is contained in:
Nick O'Leary
2021-01-27 20:32:52 +00:00
committed by GitHub
parent 34ef055d7b
commit 438d51d26e
6 changed files with 342 additions and 31 deletions

View File

@@ -119,13 +119,17 @@ describe('switch Node', function() {
* @param done - callback when done
*/
function customFlowSwitchTest(flow, shouldReceive, sendPayload, done) {
customFlowMessageSwitchTest(flow,shouldReceive,{payload: sendPayload}, done);
}
function customFlowMessageSwitchTest(flow, shouldReceive, message, done) {
helper.load(switchNode, flow, function() {
var switchNode1 = helper.getNode("switchNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
if (shouldReceive === true) {
should.equal(msg.payload,sendPayload);
should.equal(msg,message);
done();
} else {
should.fail(null, null, "We should never get an input!");
@@ -134,7 +138,7 @@ describe('switch Node', function() {
done(err);
}
});
switchNode1.receive({payload:sendPayload});
switchNode1.receive(message);
if (shouldReceive === false) {
setTimeout(function() {
done();
@@ -425,6 +429,29 @@ describe('switch Node', function() {
});
});
it('should use a nested message property to compare value - matches', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload[msg.topic]",rules:[{"t":"eq","v":"bar"}],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
customFlowMessageSwitchTest(flow, true, {topic:"foo",payload:{"foo":"bar"}}, done);
})
it('should use a nested message property to compare value - no match', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload[msg.topic]",rules:[{"t":"eq","v":"bar"}],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
customFlowMessageSwitchTest(flow, false, {topic:"foo",payload:{"foo":"none"}}, done);
})
it('should use a nested message property to compare nested message property - matches', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload[msg.topic]",rules:[{"t":"eq","v":"payload[msg.topic2]",vt:"msg"}],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
customFlowMessageSwitchTest(flow, true, {topic:"foo",topic2:"foo2",payload:{"foo":"bar","foo2":"bar"}}, done);
})
it('should use a nested message property to compare nested message property - no match', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload[msg.topic]",rules:[{"t":"eq","v":"payload[msg.topic2]",vt:"msg"}],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
customFlowMessageSwitchTest(flow, false, {topic:"foo",topic2:"foo2",payload:{"foo":"bar","foo2":"none"}}, done);
})
it('should match regex with ignore-case flag set true', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{"t":"regex","v":"onetwothree","case":true}],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];

View File

@@ -98,7 +98,7 @@ describe('change Node', function() {
});
describe('#set' , function() {
it('sets the value of the message property', function(done) {
var flow = [{"id":"changeNode1","type":"change","action":"replace","property":"payload","from":"","to":"changed","reg":false,"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
@@ -672,6 +672,111 @@ describe('change Node', function() {
});
});
it('sets the value of a message property using a nested property', function(done) {
var flow = [{"id":"changeNode1","type":"change","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"lookup[msg.topic]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"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(2);
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:"",lookup:{a:1,b:2},topic:"b"});
});
});
it('sets the value of a nested message property using a message property', function(done) {
var flow = [{"id":"changeNode1","type":"change","name":"","rules":[{"t":"set","p":"lookup[msg.topic]","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"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.lookup.b.should.equal("newValue");
done();
} catch(err) {
done(err);
}
});
var msg = {
payload: "newValue",
lookup:{a:1,b:2},
topic:"b"
}
changeNode1.receive(msg);
});
});
it('sets the value of a message property using a nested property in flow context', function(done) {
var flow = [{"id":"changeNode1","type":"change","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"lookup[msg.topic]","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"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(2);
done();
} catch(err) {
done(err);
}
});
changeNode1.context().flow.set("lookup",{a:1, b:2});
changeNode1.receive({payload: "", topic: "b"});
});
})
it('sets the value of a message property using a nested property in flow context', function(done) {
var flow = [{"id":"changeNode1","type":"change","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"lookup[msg.topic]","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"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(2);
done();
} catch(err) {
done(err);
}
});
changeNode1.context().flow.set("lookup",{a:1, b:2});
changeNode1.receive({payload: "", topic: "b"});
});
})
it('sets the value of a nested flow context property using a message property', function(done) {
var flow = [{"id":"changeNode1","type":"change","name":"","rules":[{"t":"set","p":"lookup[msg.topic]","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"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("newValue");
changeNode1.context().flow.get("lookup.b").should.eql("newValue");
done();
} catch(err) {
done(err);
}
});
changeNode1.context().flow.set("lookup",{a:1, b:2});
changeNode1.receive({payload: "newValue", topic: "b"});
});
})
});
describe('#change', function() {
it('changes the value of the message property', function(done) {

View File

@@ -164,6 +164,13 @@ describe("@node-red/util/util", function() {
var v2 = util.getMessageProperty({a:"foo"},"a");
v2.should.eql("foo");
});
it('retrieves a nested property', function() {
var v = util.getMessageProperty({a:"foo",b:{foo:1,bar:2}},"msg.b[msg.a]");
v.should.eql(1);
var v2 = util.getMessageProperty({a:"bar",b:{foo:1,bar:2}},"b[msg.a]");
v2.should.eql(2);
});
it('should return undefined if property does not exist', function() {
var v = util.getMessageProperty({a:"foo"},"msg.b");
should.not.exist(v);
@@ -331,7 +338,18 @@ describe("@node-red/util/util", function() {
msg.a[0].should.eql(1);
msg.a[1].should.eql(3);
})
it('handles nested message property references', function() {
var obj = {a:"foo",b:{}};
var result = util.setObjectProperty(obj,"b[msg.a]","bar");
result.should.be.true();
obj.b.should.have.property("foo","bar");
});
it('handles nested message property references', function() {
var obj = {a:"foo",b:{"foo":[0,0,0]}};
var result = util.setObjectProperty(obj,"b[msg.a][2]","bar");
result.should.be.true();
obj.b.foo.should.eql([0,0,"bar"])
});
});
describe('evaluateNodeProperty', function() {
@@ -459,13 +477,24 @@ describe("@node-red/util/util", function() {
// console.log(result);
result.should.eql(expected);
}
function testInvalid(input) {
function testABCWithMessage(input,msg,expected) {
var result = util.normalisePropertyExpression(input,msg);
// console.log("+",input);
// console.log(result);
result.should.eql(expected);
}
function testInvalid(input,msg) {
/*jshint immed: false */
(function() {
util.normalisePropertyExpression(input);
util.normalisePropertyExpression(input,msg);
}).should.throw();
}
function testToString(input,msg,expected) {
var result = util.normalisePropertyExpression(input,msg,true);
console.log("+",input);
console.log(result);
result.should.eql(expected);
}
it('pass a.b.c',function() { testABC('a.b.c',['a','b','c']); })
it('pass a["b"]["c"]',function() { testABC('a["b"]["c"]',['a','b','c']); })
it('pass a["b"].c',function() { testABC('a["b"].c',['a','b','c']); })
@@ -479,12 +508,25 @@ describe("@node-red/util/util", function() {
it("pass 'a.b'[1]",function() { testABC("'a.b'[1]",['a.b',1]); })
it("pass 'a.b'.c",function() { testABC("'a.b'.c",['a.b','c']); })
it("pass a[msg.b]",function() { testABC("a[msg.b]",["a",["msg","b"]]); })
it("pass a[msg[msg.b]]",function() { testABC("a[msg[msg.b]]",["a",["msg",["msg","b"]]]); })
it("pass a[msg.b]",function() { testABC("a[msg.b]",["a",["msg","b"]]); })
it("pass a[msg.b]",function() { testABC("a[msg.b]",["a",["msg","b"]]); })
it("pass a[msg['b]\"[']]",function() { testABC("a[msg['b]\"[']]",["a",["msg","b]\"["]]); })
it("pass a[msg['b][']]",function() { testABC("a[msg['b][']]",["a",["msg","b]["]]); })
it("pass b[msg.a][2]",function() { testABC("b[msg.a][2]",["b",["msg","a"],2])})
it("pass b[msg.a][2] (with message)",function() { testABCWithMessage("b[msg.a][2]",{a: "foo"},["b","foo",2])})
it('pass a.$b.c',function() { testABC('a.$b.c',['a','$b','c']); })
it('pass a["$b"].c',function() { testABC('a["$b"].c',['a','$b','c']); })
it('pass a._b.c',function() { testABC('a._b.c',['a','_b','c']); })
it('pass a["_b"].c',function() { testABC('a["_b"].c',['a','_b','c']); })
it("pass a['a.b[0]'].c",function() { testToString("a['a.b[0]'].c",null,'a["a.b[0]"]["c"]'); })
it("pass a.b.c",function() { testToString("a.b.c",null,'a["b"]["c"]'); })
it('pass a[msg.c][0]["fred"]',function() { testToString('a[msg.c][0]["fred"]',{c:"123"},'a["123"][0]["fred"]'); })
it("fail a'b'.c",function() { testInvalid("a'b'.c"); })
it("fail a['b'.c",function() { testInvalid("a['b'.c"); })
it("fail a[]",function() { testInvalid("a[]"); })
@@ -505,6 +547,12 @@ describe("@node-red/util/util", function() {
it("fail a['']",function() { testInvalid("a['']"); })
it("fail 'a.b'c",function() { testInvalid("'a.b'c"); })
it("fail <blank>",function() { testInvalid("");})
it("fail a[b]",function() { testInvalid("a[b]"); })
it("fail a[msg.]",function() { testInvalid("a[msg.]"); })
it("fail a[msg[]",function() { testInvalid("a[msg[]"); })
it("fail a[msg.[]]",function() { testInvalid("a[msg.[]]"); })
it("fail a[msg['af]]",function() { testInvalid("a[msg['af]]"); })
it("fail b[msg.undefined][2] (with message)",function() { testInvalid("b[msg.undefined][2]",{})})
});
@@ -983,4 +1031,5 @@ describe("@node-red/util/util", function() {
});
});
});