mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'dev' into pr_4387
This commit is contained in:
@@ -2538,38 +2538,4 @@ describe('HTTP Request Node', function() {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('multipart form posts', function() {
|
||||
it('should send arrays as multiple entries', function (done) {
|
||||
const flow = [
|
||||
{
|
||||
id: 'n1', type: 'http request', wires: [['n2']], method: 'POST', ret: 'obj', url: getTestURL('/file-upload'), headers: [
|
||||
]
|
||||
},
|
||||
{ id: "n2", type: "helper" }
|
||||
];
|
||||
helper.load(httpRequestNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on('input', function(msg){
|
||||
try {
|
||||
msg.payload.body.should.have.property('foo')
|
||||
msg.payload.body.list.should.deepEqual(['a','b','c'])
|
||||
done()
|
||||
} catch (e) {
|
||||
done(e)
|
||||
}
|
||||
});
|
||||
n1.receive({
|
||||
headers: {
|
||||
'content-type': 'multipart/form-data'
|
||||
},
|
||||
payload: {
|
||||
foo: 'bar',
|
||||
list: [ 'a', 'b', 'c' ]
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
})
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -66,6 +66,27 @@ describe('SPLIT node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should split an array on a sub-property into multiple messages', function(done) {
|
||||
var flow = [{id:"sn1", type:"split", property:"foo", wires:[["sn2"]]},
|
||||
{id:"sn2", type:"helper"}];
|
||||
helper.load(splitNode, flow, function() {
|
||||
var sn1 = helper.getNode("sn1");
|
||||
var sn2 = helper.getNode("sn2");
|
||||
sn2.on("input", function(msg) {
|
||||
msg.should.have.property("parts");
|
||||
msg.parts.should.have.property("count",4);
|
||||
msg.parts.should.have.property("type","array");
|
||||
msg.parts.should.have.property("index");
|
||||
msg.parts.should.have.property("property","foo");
|
||||
if (msg.parts.index === 0) { msg.foo.should.equal(1); }
|
||||
if (msg.parts.index === 1) { msg.foo.should.equal(2); }
|
||||
if (msg.parts.index === 2) { msg.foo.should.equal(3); }
|
||||
if (msg.parts.index === 3) { msg.foo.should.equal(4); done(); }
|
||||
});
|
||||
sn1.receive({foo:[1,2,3,4]});
|
||||
});
|
||||
});
|
||||
|
||||
it('should split an array into multiple messages of a specified size', function(done) {
|
||||
var flow = [{id:"sn1", type:"split", wires:[["sn2"]], arraySplt:3, arraySpltType:"len"},
|
||||
{id:"sn2", type:"helper"}];
|
||||
@@ -108,6 +129,31 @@ describe('SPLIT node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should split an object sub property into pieces', function(done) {
|
||||
var flow = [{id:"sn1", type:"split", property:"foo.bar",wires:[["sn2"]]},
|
||||
{id:"sn2", type:"helper"}];
|
||||
helper.load(splitNode, flow, function() {
|
||||
var sn1 = helper.getNode("sn1");
|
||||
var sn2 = helper.getNode("sn2");
|
||||
var count = 0;
|
||||
sn2.on("input", function(msg) {
|
||||
msg.should.have.property("foo");
|
||||
msg.foo.should.have.property("bar");
|
||||
msg.should.have.property("parts");
|
||||
msg.parts.should.have.property("type","object");
|
||||
msg.parts.should.have.property("key");
|
||||
msg.parts.should.have.property("count");
|
||||
msg.parts.should.have.property("index");
|
||||
msg.parts.should.have.property("property","foo.bar");
|
||||
msg.topic.should.equal("foo");
|
||||
if (msg.parts.index === 0) { msg.foo.bar.should.equal(1); }
|
||||
if (msg.parts.index === 1) { msg.foo.bar.should.equal("2"); }
|
||||
if (msg.parts.index === 2) { msg.foo.bar.should.equal(true); done(); }
|
||||
});
|
||||
sn1.receive({topic:"foo",foo:{bar:{a:1,b:"2",c:true}}});
|
||||
});
|
||||
});
|
||||
|
||||
it('should split an object into pieces and overwrite their topics', function(done) {
|
||||
var flow = [{id:"sn1", type:"split", addname:"topic", wires:[["sn2"]]},
|
||||
{id:"sn2", type:"helper"}];
|
||||
@@ -516,6 +562,7 @@ describe('JOIN node', function() {
|
||||
n1.receive({payload:{a:1}});
|
||||
});
|
||||
});
|
||||
|
||||
it('should join things into an array ignoring msg.parts.index in manual mode', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joiner:",",mode:"custom"},
|
||||
{id:"n2", type:"helper"}];
|
||||
@@ -562,6 +609,32 @@ describe('JOIN node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should join things into an array on a sub property in auto mode', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joiner:",", mode:"auto"},
|
||||
{id:"n2", type:"helper"}];
|
||||
helper.load(joinNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("foo");
|
||||
msg.foo.should.have.property("bar");
|
||||
msg.foo.bar.should.be.an.Array();
|
||||
msg.foo.bar[0].should.equal("A");
|
||||
msg.foo.bar[1].should.equal("B");
|
||||
//msg.payload[2].a.should.equal(1);
|
||||
done();
|
||||
}
|
||||
catch(e) {done(e);}
|
||||
});
|
||||
n1.receive({foo:{bar:"A"}, parts:{id:1, type:"array", len:1, index:0, count:4, property:"foo.bar"}});
|
||||
n1.receive({foo:{bar:"B"}, parts:{id:1, type:"array", len:1, index:1, count:4, property:"foo.bar"}});
|
||||
n1.receive({foo:{bar:"C"}, parts:{id:1, type:"array", len:1, index:2, count:4, property:"foo.bar"}});
|
||||
n1.receive({foo:{bar:"D"}, parts:{id:1, type:"array", len:1, index:3, count:4, property:"foo.bar"}});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should join strings into a buffer after a count', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:2, build:"buffer", joinerType:"bin", joiner:"", mode:"custom"},
|
||||
{id:"n2", type:"helper"}];
|
||||
@@ -639,6 +712,35 @@ describe('JOIN node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge sub property objects', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:5, property:"foo.bar", build:"merged", mode:"custom"},
|
||||
{id:"n2", type:"helper"}];
|
||||
helper.load(joinNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("foo");
|
||||
msg.foo.should.have.property("bar");
|
||||
msg.foo.bar.should.have.property("a",1);
|
||||
msg.foo.bar.should.have.property("b",2);
|
||||
msg.foo.bar.should.have.property("c",3);
|
||||
msg.foo.bar.should.have.property("d",4);
|
||||
msg.foo.bar.should.have.property("e",5);
|
||||
done();
|
||||
}
|
||||
catch(e) { done(e)}
|
||||
});
|
||||
n1.receive({foo:{bar:{a:9}, topic:"f"}});
|
||||
n1.receive({foo:{bar:{a:1}, topic:"a"}});
|
||||
n1.receive({foo:{bar:{b:9}, topic:"b"}});
|
||||
n1.receive({foo:{bar:{b:2}, topic:"b"}});
|
||||
n1.receive({foo:{bar:{c:3}, topic:"c"}});
|
||||
n1.receive({foo:{bar:{d:4}, topic:"d"}});
|
||||
n1.receive({foo:{bar:{e:5}, topic:"e"}});
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge full msg objects', function(done) {
|
||||
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:6, build:"merged", mode:"custom", propertyType:"full", property:""},
|
||||
{id:"n2", type:"helper"}];
|
||||
|
@@ -29,7 +29,7 @@ describe("api/editor/ui", function() {
|
||||
var app;
|
||||
|
||||
before(function() {
|
||||
ui.init({
|
||||
ui.init({}, {
|
||||
nodes: {
|
||||
getIcon: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
|
@@ -33,16 +33,15 @@ describe("library api", function() {
|
||||
should.not.exist(library.getExampleFlowPath('foo','bar'));
|
||||
});
|
||||
|
||||
it('returns a valid example path', function(done) {
|
||||
it('returns valid example paths', function(done) {
|
||||
library.init();
|
||||
library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
|
||||
try {
|
||||
var flows = library.getExampleFlows();
|
||||
flows.should.deepEqual({"test-module":{"f":["one"]}});
|
||||
flows.should.deepEqual({"test-module":{"f":["1.2.3","one"]}});
|
||||
|
||||
var examplePath = library.getExampleFlowPath('test-module','one');
|
||||
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'))
|
||||
|
||||
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'));
|
||||
|
||||
library.removeExamplesDir('test-module');
|
||||
|
||||
@@ -57,6 +56,5 @@ describe("library api", function() {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
});
|
||||
|
@@ -26,6 +26,7 @@ var flowUtils = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/util");
|
||||
var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Flow");
|
||||
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows");
|
||||
var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
|
||||
var credentials = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/credentials");
|
||||
var hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
|
||||
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry");
|
||||
|
||||
@@ -61,6 +62,7 @@ describe('Flow', function() {
|
||||
this.scope = n.scope;
|
||||
var node = this;
|
||||
this.foo = n.foo;
|
||||
this.bar = n.bar;
|
||||
this.handled = 0;
|
||||
this.stopped = false;
|
||||
currentNodes[node.id] = node;
|
||||
@@ -1235,11 +1237,12 @@ describe('Flow', function() {
|
||||
})
|
||||
|
||||
describe("#env", function () {
|
||||
afterEach(() => {
|
||||
delete process.env.V0;
|
||||
delete process.env.V1;
|
||||
credentials.get.restore?.()
|
||||
})
|
||||
it("can instantiate a node with environment variable property values of group and tab", async function () {
|
||||
after(function() {
|
||||
delete process.env.V0;
|
||||
delete process.env.V1;
|
||||
})
|
||||
process.env.V0 = "gv0";
|
||||
process.env.V1 = "gv1";
|
||||
process.env.V3 = "gv3";
|
||||
@@ -1283,10 +1286,6 @@ describe('Flow', function() {
|
||||
});
|
||||
|
||||
it("can access environment variable property using $parent", async function () {
|
||||
after(function() {
|
||||
delete process.env.V0;
|
||||
delete process.env.V1;
|
||||
})
|
||||
process.env.V0 = "gv0";
|
||||
process.env.V1 = "gv1";
|
||||
var config = flowUtils.parseConfig([
|
||||
@@ -1321,9 +1320,6 @@ describe('Flow', function() {
|
||||
});
|
||||
|
||||
it("can define environment variable using JSONata", async function () {
|
||||
after(function() {
|
||||
delete process.env.V0;
|
||||
})
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab",env:[
|
||||
{"name": "V0", value: "1+2", type: "jsonata"}
|
||||
@@ -1346,9 +1342,6 @@ describe('Flow', function() {
|
||||
});
|
||||
|
||||
it("can access global environment variables defined as JSONata values", async function () {
|
||||
after(function() {
|
||||
delete process.env.V0;
|
||||
})
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab",env:[
|
||||
{"name": "V0", value: "1+2", type: "jsonata"}
|
||||
@@ -1370,15 +1363,21 @@ describe('Flow', function() {
|
||||
await flow.stop()
|
||||
});
|
||||
it("global flow can access global-config defined environment variables", async function () {
|
||||
after(function() {
|
||||
delete process.env.V0;
|
||||
sinon.stub(credentials,"get").callsFake(function(id) {
|
||||
if (id === 'gc') {
|
||||
return { map: { GC_CRED: 'gc_cred' }}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const config = flowUtils.parseConfig([
|
||||
{id:"gc", type:"global-config", env:[
|
||||
{"name": "GC0", value: "3+4", type: "jsonata"}
|
||||
{"name": "GC0", value: "3+4", type: "jsonata"},
|
||||
{"name": "GC_CRED", type: "cred"},
|
||||
|
||||
]},
|
||||
{id:"t1",type:"tab" },
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"${GC0}",wires:[]},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"${GC0}", bar:"${GC_CRED}", wires:[]},
|
||||
]);
|
||||
// Two-arg call - makes this the global flow that handles global-config nodes
|
||||
const globalFlow = Flow.create({getSetting:v=>process.env[v]},config);
|
||||
@@ -1390,6 +1389,7 @@ describe('Flow', function() {
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
activeNodes["1"].foo.should.equal(7);
|
||||
activeNodes["1"].bar.should.equal('gc_cred');
|
||||
|
||||
await flow.stop()
|
||||
await globalFlow.stop()
|
||||
|
@@ -183,6 +183,35 @@ describe('Node', function() {
|
||||
n.receive(message);
|
||||
});
|
||||
|
||||
|
||||
it('calls parent flow handleComplete when multiple callbacks provided', function(done) {
|
||||
var n = new RedNode({id:'123',type:'abc', _flow: {
|
||||
handleComplete: function(node,msg) {
|
||||
try {
|
||||
doneCount.should.equal(2)
|
||||
msg.should.deepEqual(message);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
}
|
||||
}});
|
||||
|
||||
var message = {payload:"hello world"};
|
||||
let doneCount = 0
|
||||
n.on('input',function(msg, nodeSend, nodeDone) {
|
||||
doneCount++
|
||||
nodeDone();
|
||||
});
|
||||
// Include a callback without explicit done signature
|
||||
n.on('input',function(msg) { });
|
||||
n.on('input',function(msg, nodeSend, nodeDone) {
|
||||
doneCount++
|
||||
nodeDone();
|
||||
});
|
||||
n.receive(message);
|
||||
});
|
||||
|
||||
it('triggers onComplete hook when done callback provided', function(done) {
|
||||
var handleCompleteCalled = false;
|
||||
var hookCalled = false;
|
||||
|
@@ -379,10 +379,17 @@ describe("@node-red/util/util", function() {
|
||||
result = util.evaluateNodeProperty('','bool');
|
||||
result.should.be.false();
|
||||
});
|
||||
it('returns date',function() {
|
||||
it('returns date - default format',function() {
|
||||
var result = util.evaluateNodeProperty('','date');
|
||||
(Date.now() - result).should.be.approximately(0,50);
|
||||
});
|
||||
|
||||
it('returns date - iso format',function() {
|
||||
var result = util.evaluateNodeProperty('iso','date');
|
||||
// 2023-12-04T16:51:04.429Z
|
||||
/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d+Z$/.test(result).should.be.true()
|
||||
});
|
||||
|
||||
it('returns bin', function () {
|
||||
var result = util.evaluateNodeProperty('[1, 2]','bin');
|
||||
result[0].should.eql(1);
|
||||
@@ -441,9 +448,16 @@ describe("@node-red/util/util", function() {
|
||||
},{});
|
||||
result.should.eql("123");
|
||||
});
|
||||
it('returns jsonata result', function () {
|
||||
var result = util.evaluateNodeProperty('$abs(-1)','jsonata',{},{});
|
||||
result.should.eql(1);
|
||||
it('returns jsonata result', function (done) {
|
||||
util.evaluateNodeProperty('$abs(-1)','jsonata',{},{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql(1);
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
it('returns null', function() {
|
||||
var result = util.evaluateNodeProperty(null,'null');
|
||||
@@ -601,51 +615,105 @@ describe("@node-red/util/util", function() {
|
||||
});
|
||||
});
|
||||
describe('evaluateJSONataExpression', function() {
|
||||
it('evaluates an expression', function() {
|
||||
it('evaluates an expression', function(done) {
|
||||
var expr = util.prepareJSONataExpression('payload',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
result.should.eql("hello");
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("hello");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('evaluates a legacyMode expression', function() {
|
||||
var expr = util.prepareJSONataExpression('msg.payload',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
result.should.eql("hello");
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("hello");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses flow context from an expression', function() {
|
||||
var expr = util.prepareJSONataExpression('$flowContext("foo")',{context:function() { return {flow:{get: function(key) { return {'foo':'bar'}[key]}}}}});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
result.should.eql("bar");
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("bar");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses undefined environment variable from an expression', function() {
|
||||
var expr = util.prepareJSONataExpression('$env("UTIL_ENV")',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{});
|
||||
result.should.eql('');
|
||||
});
|
||||
util.evaluateJSONataExpression(expr,{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses environment variable from an expression', function() {
|
||||
process.env.UTIL_ENV = 'foo';
|
||||
var expr = util.prepareJSONataExpression('$env("UTIL_ENV")',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{});
|
||||
result.should.eql('foo');
|
||||
});
|
||||
util.evaluateJSONataExpression(expr,{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("foo");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses moment from an expression', function() {
|
||||
var expr = util.prepareJSONataExpression('$moment("2020-05-27", "YYYY-MM-DD").add(7, "days").add(1, "months").format("YYYY-MM-DD")',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{});
|
||||
result.should.eql('2020-07-03');
|
||||
util.evaluateJSONataExpression(expr,{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("2020-07-03");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('accesses moment-timezone from an expression', function() {
|
||||
var expr = util.prepareJSONataExpression('$moment("2013-11-18 11:55Z").tz("Asia/Taipei").format()',{});
|
||||
var result = util.evaluateJSONataExpression(expr,{});
|
||||
result.should.eql('2013-11-18T19:55:00+08:00');
|
||||
util.evaluateJSONataExpression(expr,{}, (err, result) => {
|
||||
try {
|
||||
result.should.eql("2013-11-18T19:55:00+08:00");
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('handles non-existant flow context variable', function() {
|
||||
var expr = util.prepareJSONataExpression('$flowContext("nonExistant")',{context:function() { return {flow:{get: function(key) { return {'foo':'bar'}[key]}}}}});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
should.not.exist(result);
|
||||
});
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
should.not.exist(result);
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('handles non-existant global context variable', function() {
|
||||
var expr = util.prepareJSONataExpression('$globalContext("nonExistant")',{context:function() { return {global:{get: function(key) { return {'foo':'bar'}[key]}}}}});
|
||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||
should.not.exist(result);
|
||||
util.evaluateJSONataExpression(expr,{payload:"hello"}, (err, result) => {
|
||||
try {
|
||||
should.not.exist(result);
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('handles async flow context access', function(done) {
|
||||
var expr = util.prepareJSONataExpression('$flowContext("foo")',{context:function() { return {flow:{get: function(key,store,callback) { setTimeout(()=>{callback(null,{'foo':'bar'}[key])},10)}}}}});
|
||||
|
Reference in New Issue
Block a user