Reorganise nodes into new categories

This commit is contained in:
Nick O'Leary
2019-07-09 23:32:22 +01:00
parent 2b66723d42
commit da6db24f9e
148 changed files with 36 additions and 37 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,152 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var rangeNode = require("nr-test-utils").require("@node-red/nodes/core/function/16-range.js");
var helper = require("node-red-node-test-helper");
describe('range Node', function() {
beforeEach(function(done) {
helper.startServer(done);
});
afterEach(function(done) {
helper.unload();
helper.stopServer(done);
});
it('should load some defaults', function(done) {
var flow = [{"id":"rangeNode1","type":"range","name":"rangeNode"}];
helper.load(rangeNode, flow, function() {
var rangeNode1 = helper.getNode("rangeNode1");
rangeNode1.should.have.property('name', 'rangeNode');
rangeNode1.should.have.property('round', false);
done();
});
});
/**
* Run a generic range test
* @param action - scale/clamp (range limit)/roll (modulo): what action to choose
* @param minin - map from minimum value
* @param maxin - map from maximum value
* @param minout - map to minimum value
* @param maxout - map to maximum value
* @param round - whether to round the result to the nearest integer
* @param aPayload - what payload to send to the range node
* @param expectedResult - what result we're expecting
* @param done - the callback to call when test done
*/
function genericRangeTest(action, minin, maxin, minout, maxout, round, aPayload, expectedResult, done) {
var flow = [{"id":"rangeNode1","type":"range","minin":minin,"maxin":maxin,"minout":minout,"maxout":maxout,"action":action,"round":round,"name":"rangeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(rangeNode, flow, function() {
var rangeNode1 = helper.getNode("rangeNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
msg.payload.should.equal(expectedResult);
done();
} catch(err) {
done(err);
}
});
rangeNode1.receive({payload:aPayload});
});
}
it('ranges numbers up tenfold', function(done) {
genericRangeTest("scale", 0, 100, 0, 1000, false, 50, 500, done);
});
it('ranges numbers down such as centimetres to metres', function(done) {
genericRangeTest("scale", 0, 100, 0, 1, false, 55, 0.55, done);
});
it('wraps numbers down say for degree/rotation reading 1/2', function(done) {
genericRangeTest("roll", 0, 10, 0, 360, true, 15, 180, done); // 1/2 around wrap => "one and a half turns"
});
it('wraps numbers around say for degree/rotation reading 1/3', function(done) {
genericRangeTest("roll", 0, 10, 0, 360, true, 13.3333, 120, done); // 1/3 around wrap => "one and a third turns"
});
it('wraps numbers around say for degree/rotation reading 1/4', function(done) {
genericRangeTest("roll", 0, 10, 0, 360, true, 12.5, 90, done); // 1/4 around wrap => "one and a quarter turns"
});
it('wraps numbers down say for degree/rotation reading 1/4', function(done) {
genericRangeTest("roll", 0, 10, 0, 360, true, -12.5, 270, done); // 1/4 backwards wrap => "one and a quarter turns backwards"
});
it('wraps numbers around say for degree/rotation reading 0', function(done) {
genericRangeTest("roll", 0, 10, 0, 360, true, -10, 0, done);
});
it('clamps numbers within a range - over max', function(done) {
genericRangeTest("clamp", 0, 10, 0, 1000, false, 111, 1000, done);
});
it('clamps numbers within a range - below min', function(done) {
genericRangeTest("clamp", 0, 10, 0, 1000, false, -1, 0, done);
});
it('just passes on msg if payload not present', function(done) {
var flow = [{"id":"rangeNode1","type":"range","minin":0,"maxin":100,"minout":0,"maxout":100,"action":"scale","round":true,"name":"rangeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(rangeNode, flow, function() {
var rangeNode1 = helper.getNode("rangeNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
msg.should.not.have.property('payload');
msg.topic.should.equal("pass on");
done();
} catch(err) {
done(err);
}
});
rangeNode1.receive({topic:"pass on"});
});
});
it('reports if input is not a number', function(done) {
var flow = [{"id":"rangeNode1","type":"range","minin":0,"maxin":0,"minout":0,"maxout":0,"action":"scale","round":true,"name":"rangeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(rangeNode, flow, function() {
var rangeNode1 = helper.getNode("rangeNode1");
var helperNode1 = helper.getNode("helperNode1");
rangeNode1.on("call:log",function(args) {
var log = args.args[0];
if (log.indexOf("notnumber") > -1) {
rangeNode1.log.restore();
done();
} else {
try {
should.fail(null, null, "Non-number inputs should be reported!");
} catch (err) {
rangeNode1.log.restore();
done(err);
}
}
});
rangeNode1.receive({payload:"NOT A NUMBER"});
});
});
});

View File

@@ -0,0 +1,498 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var templateNode = require("nr-test-utils").require("@node-red/nodes/core/function/80-template.js");
var Context = require("nr-test-utils").require("@node-red/runtime/lib/nodes/context");
var helper = require("node-red-node-test-helper");
describe('template node', function() {
before(function(done) {
helper.startServer(done);
});
after(function(done) {
helper.stopServer(done);
});
beforeEach(function(done) {
done();
});
function initContext(done) {
Context.init({
contextStorage: {
memory0: { // do not use (for excluding effect fallback)
module: "memory"
},
memory1: {
module: "memory"
},
memory2: {
module: "memory"
}
}
});
Context.load().then(function () {
done();
});
}
afterEach(function() {
helper.unload().then(function () {
return Context.clean({allNodes:{}});
}).then(function () {
return Context.close();
});
});
it('should modify payload using node-configured template', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
msg.should.have.property('template', 'this should be ignored as the node has its own template {{payload}}');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo",topic: "bar", template: "this should be ignored as the node has its own template {{payload}}"});
});
});
it('should modify the configured property using msg.template', function(done) {
var flow = [{id:"n1", type:"template", field:"randomProperty", template:"",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
msg.should.have.property('template', 'payload={{payload}}');
msg.should.have.property('randomProperty', 'payload=foo');
done();
});
n1.receive({payload:"foo", topic: "bar", template: "payload={{payload}}"});
});
});
it('should be able to overwrite msg.template using the template from msg.template', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", template:"",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'topic=bar');
msg.should.have.property('template', 'topic={{topic}}');
done();
});
n1.receive({payload:"foo", topic: "bar", template: "topic={{topic}}"});
});
});
it('should modify payload from msg.template', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", template:"",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var received = [];
n2.on("input", function(msg) {
try {
received.push(msg);
if (received.length === 3) {
received[0].should.have.property('topic', 'bar');
received[0].should.have.property('payload', 'topic=bar');
received[0].should.have.property('template', 'topic={{topic}}');
received[1].should.have.property('topic', 'another bar');
received[1].should.have.property('payload', 'topic=another bar');
received[1].should.have.property('template', 'topic={{topic}}');
received[2].should.have.property('topic', 'bar');
received[2].should.have.property('payload', 'payload=foo');
received[2].should.have.property('template', 'payload={{payload}}');
done();
}
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo", topic: "bar", template: "topic={{topic}}"});
n1.receive({payload:"foo", topic: "another bar", template: "topic={{topic}}"});
n1.receive({payload:"foo", topic: "bar", template: "payload={{payload}}"});
});
});
it('should modify payload from flow context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{flow.value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.context().flow.set("value","foo");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should modify payload from persistable flow context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{flow[memory1].value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
done();
});
n1.context().flow.set("value","foo","memory1",function (err) {
n1.receive({payload:"foo",topic: "bar"});
});
});
});
});
it('should handle nested context tags - property not set', function(done) {
// This comes from the Coursera Node-RED course and is a good example of
// multiple conditional tags
var template = `{{#flow.time}}time={{flow.time}}{{/flow.time}}{{^flow.time}}!time{{/flow.time}}{{#flow.random}}random={{flow.random}}randomtime={{flow.randomtime}}{{/flow.random}}{{^flow.random}}!random{{/flow.random}}`;
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:template,wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
initContext(function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', '!time!random');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo",topic: "bar"});
});
});
})
it('should handle nested context tags - property set', function(done) {
// This comes from the Coursera Node-RED course and is a good example of
// multiple conditional tags
var template = `{{#flow.time}}time={{flow.time}}{{/flow.time}}{{^flow.time}}!time{{/flow.time}}{{#flow.random}}random={{flow.random}}randomtime={{flow.randomtime}}{{/flow.random}}{{^flow.random}}!random{{/flow.random}}`;
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:template,wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
initContext(function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'time=123random=456randomtime=789');
done();
} catch(err) {
done(err);
}
});
n1.context().flow.set(["time","random","randomtime"],["123","456","789"],function (err) {
n1.receive({payload:"foo",topic: "bar"});
});
});
});
})
it('should modify payload from two persistable flow context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{flow[memory1].value}}/{{flow[memory2].value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
initContext(function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo/bar');
done();
});
n1.context().flow.set("value","foo","memory1",function (err) {
n1.context().flow.set("value","bar","memory2",function (err) {
n1.receive({payload:"foo",topic: "bar"});
});
});
});
});
});
it('should modify payload from global context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{global.value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.context().global.set("value","foo");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should modify payload from persistable global context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{global[memory1].value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
done();
});
n1.context().global.set("value","foo","memory1", function (err) {
n1.receive({payload:"foo",topic: "bar"});
});
});
});
});
it('should modify payload from two persistable global context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{global[memory1].value}}/{{global[memory2].value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo/bar');
done();
});
n1.context().global.set("value","foo","memory1", function (err) {
n1.context().global.set("value","bar","memory2", function (err) {
n1.receive({payload:"foo",topic: "bar"});
});
});
});
});
});
it('should modify payload from persistable flow & global context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{flow[memory1].value}}/{{global[memory1].value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo/bar');
done();
});
n1.context().flow.set("value","foo","memory1", function (err) {
n1.context().global.set("value","bar","memory1", function (err) {
n1.receive({payload:"foo",topic: "bar"});
});
});
});
});
});
it('should handle missing node context', function(done) {
// this is artificial test because in flow there is missing z property (probably never happen in real usage)
var flow = [{id:"n1",type:"template", field:"payload", template:"payload={{flow.value}},{{global.value}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=,');
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should handle escape characters in Mustache format and JSON output mode', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", syntax:"mustache", template:"{\"data\":\"{{payload}}\"}", output:"json", wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.payload.should.have.property('data', 'line\t1\nline\\2\r\nline\b3\f');
done();
});
n1.receive({payload:"line\t1\nline\\2\r\nline\b3\f"});
});
});
it('should modify payload in plain text mode', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", syntax:"plain", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload={{payload}}');
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should modify flow context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", fieldType:"flow", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
// mesage is intact
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
// result is in flow context
n2.context().flow.get("payload").should.equal("payload=foo");
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should modify persistable flow context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"#:(memory1)::payload", fieldType:"flow", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
// mesage is intact
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
// result is in flow context
n2.context().flow.get("payload", "memory1", function (err, val) {
val.should.equal("payload=foo");
done();
});
});
n1.receive({payload:"foo",topic: "bar"});
});
});
});
it('should modify global context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", fieldType:"global", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
// mesage is intact
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
// result is in global context
n2.context().global.get("payload").should.equal("payload=foo");
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should modify persistable global context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"#:(memory1)::payload", fieldType:"global", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
// mesage is intact
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
// result is in global context
n2.context().global.get("payload", "memory1", function (err, val) {
val.should.equal("payload=foo");
done();
});
});
n1.receive({payload:"foo",topic: "bar"});
});
});
});
it('should handle if the field isn\'t set', function(done) {
var flow = [{id:"n1", type:"template", template: "payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
done();
});
n1.receive({payload:"foo",topic: "bar"});
});
});
it('should handle deeper objects', function(done) {
var flow = [{id:"n1", type:"template", field: "topic.foo.bar", template: "payload={{payload.doh.rei.me}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic');
msg.topic.should.have.property('foo');
msg.topic.foo.should.have.a.property('bar', 'payload=foo');
done();
});
n1.receive({payload:{doh:{rei:{me:"foo"}}}});
});
});
it('should handle block contexts objects', function(done) {
var flow = [{id:"n1", type:"template", template: "A{{#payload.A}}{{payload.A}}{{.}}{{/payload.A}}B",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload','AabcabcB');
done();
});
n1.receive({payload:{A:"abc"}});
});
});
it('should raise error if passed bad template', function(done) {
var flow = [{id:"n1", type:"template", field: "payload", template: "payload={{payload",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
setTimeout(function() {
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "template";
});
logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("Unclosed tag at ");
done();
},25);
n1.receive({payload:"foo"});
});
});
});

View File

@@ -0,0 +1,721 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var delayNode = require("nr-test-utils").require("@node-red/nodes/core/function/89-delay.js");
var helper = require("node-red-node-test-helper");
var GRACE_PERCENTAGE=10;
var nanosToSeconds = 1000000000;
var millisToSeconds = 1000;
var secondsToMinutes = 60;
var secondsToHours = 3600;
var secondsToDays = 86400;
describe('delay Node', function() {
beforeEach(function(done) {
helper.startServer(done);
});
afterEach(function(done) {
helper.unload();
helper.stopServer(done);
});
it('should be loaded', function(done) {
var flow = [{"id":"delayNode1","type":"delay", "nbRateUnits":"1", "name":"delayNode","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","rateUnits":"day","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[[]]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
delayNode1.should.have.property('name', 'delayNode');
delayNode1.should.have.property('rate', 86400000);
done();
});
});
it('should be able to set rate to hour', function(done) {
var flow = [{"id":"delayNode1","type":"delay", "nbRateUnits":"1", "name":"delayNode","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","rateUnits":"hour","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[[]]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
delayNode1.should.have.property('name', 'delayNode');
delayNode1.should.have.property('rate', 3600000);
done();
});
});
it('should be able to set rate to minute', function(done) {
var flow = [{"id":"delayNode1","type":"delay", "nbRateUnits":"1", "name":"delayNode","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[[]]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
delayNode1.should.have.property('name', 'delayNode');
delayNode1.should.have.property('rate', 60000);
done();
});
});
var TimeUnitEnum = {
MILLIS : "milliseconds",
SECONDS : "seconds",
MINUTES : "minutes",
HOURS : "hours",
DAYS : "days"
}
/**
* Tells whether two numeric values are close enough to each other
* @param actualValue - the value we're testing
* @param expectedValue - the value we're matching the test value against
* @param tolerancePercent - the percentage of tolerated deviation (0 means equals)
*/
function closeEnough(actualValue, expectedValue, tolerancePercent) {
var toReturn;
var toleranceFraction = expectedValue * (tolerancePercent/100);
var minExpected = expectedValue - toleranceFraction;
var maxExpected = expectedValue + toleranceFraction;
if (actualValue >= minExpected && actualValue <= maxExpected) {
toReturn = true;
} else {
toReturn = false;
}
return toReturn;
}
/**
* Runs a delay test
* @param aTimeout - the timeout quantity
* @param aTimeoutUnit - the unit of the timeout: milliseconds, seconds, minutes, hours, days
*/
function genericDelayTest(aTimeout, aTimeoutUnit, done) {
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"delay","timeout":aTimeout,"timeoutUnits":aTimeoutUnit,"rate":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
var endTime = process.hrtime(startTime);
var runtimeNanos = ( (endTime[0] * nanosToSeconds) + endTime[1] );
var runtimeSeconds = runtimeNanos / nanosToSeconds;
var aTimeoutUnifiedToSeconds;
// calculating the timeout in seconds
if (aTimeoutUnit == TimeUnitEnum.MILLIS) {
aTimeoutUnifiedToSeconds = aTimeout / millisToSeconds;
} else if (aTimeoutUnit == TimeUnitEnum.SECONDS) {
aTimeoutUnifiedToSeconds = aTimeout;
} else if (aTimeoutUnit == TimeUnitEnum.MINUTES) {
aTimeoutUnifiedToSeconds = aTimeout * secondsToMinutes;
} else if (aTimeoutUnit == TimeUnitEnum.HOURS) {
aTimeoutUnifiedToSeconds = aTimeout * secondsToHours;
} else if (aTimeoutUnit == TimeUnitEnum.DAYS) {
aTimeoutUnifiedToSeconds = aTimeout * secondsToDays;
}
if (closeEnough(runtimeSeconds, aTimeoutUnifiedToSeconds, GRACE_PERCENTAGE)) {
done();
} else {
try {
should.fail(null, null, "Delayed runtime seconds " + runtimeSeconds + " was not close enough to exlected timeout seconds: " + aTimeoutUnifiedToSeconds);
} catch (err) {
done(err);
}
}
} catch(err) {
done(err);
}
});
var startTime = process.hrtime();
delayNode1.receive({payload:"delayMe"});
});
}
/**
* We send a message, take a timestamp then when the message is received by the helper node, we take another timestamp.
* Then check if the message has been delayed by the expected amount.
*/
it('delays the message in seconds', function(done) {
genericDelayTest(0.5, "seconds", done);
});
it('delays the message in milliseconds', function(done) {
genericDelayTest(500, "milliseconds", done);
});
it('delays the message in minutes', function(done) { // this is also 0.5 seconds
genericDelayTest(0.00833, "minutes", done);
});
it('delays the message in hours', function(done) { // this is also 0.5 seconds
genericDelayTest(0.0001388, "hours", done);
});
it('delays the message in days', function(done) { // this is also 0.5 seconds
genericDelayTest(0.000005787, "days", done);
});
/**
* Runs a rate limit test - only testing seconds!
* @param aLimit - the message limit count
* @param nbUnit - the multiple of the unit, aLimit Message for nbUnit Seconds
* @param runtimeInMillis - when to terminate run and count messages received
*/
function genericRateLimitSECONDSTest(aLimit, nbUnit, runtimeInMillis, done) {
var flow = [{"id":"delayNode1","type":"delay","nbRateUnits":nbUnit,"name":"delayNode","pauseType":"rate","timeout":5,"timeoutUnits":"seconds","rate":aLimit,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
var receivedMessagesStack = [];
var rate = 1000/aLimit;
var receiveTimestamp;
helperNode1.on("input", function(msg) {
if (receiveTimestamp) {
var elapse = process.hrtime(receiveTimestamp);
var receiveInterval = (elapse[0] * 1000) + ((elapse[1] / nanosToSeconds) * 1000);
receiveInterval.should.be.above(rate * 0.9);
}
receiveTimestamp = process.hrtime();
receivedMessagesStack.push(msg);
});
var possibleMaxMessageCount = Math.ceil(aLimit * (runtimeInMillis / 1000) + aLimit); // +aLimit as at the start of the 2nd period, we're allowing the 3rd burst
var i = 0;
for (; i < possibleMaxMessageCount + 1; i++) {
delayNode1.receive({payload:i});
}
setTimeout(function() {
try {
receivedMessagesStack.length.should.be.lessThan(possibleMaxMessageCount);
for (var j = 0; j < receivedMessagesStack.length; j++) {
if (receivedMessagesStack[j].payload === j) {
if (j === (receivedMessagesStack.length -1)) { // last message, all matched so far
done();
}
} else {
should.fail(null, null, "Received messages were not received in order. Message was " + receivedMessagesStack[i].payload + " on count " + i);
}
}
} catch (err) {
done(err);
}
}, runtimeInMillis);
});
}
it('limits the message rate to 1 per second', function(done) {
genericRateLimitSECONDSTest(1, 1, 1500, done);
});
it('limits the message rate to 1 per 2 seconds', function(done) {
this.timeout(6000);
genericRateLimitSECONDSTest(1, 2, 3000, done);
});
it('limits the message rate to 2 per seconds, 2 seconds', function(done) {
this.timeout(6000);
genericRateLimitSECONDSTest(2, 1, 2100, done);
});
/**
* Runs a rate limit test with drop support - only testing seconds!
* @param aLimit - the message limit count
* @param nbUnit - the multiple of the unit, aLimit Message for nbUnit Seconds
* @param runtimeInMillis - when to terminate run and count messages received
*/
function dropRateLimitSECONDSTest(aLimit, nbUnit, runtimeInMillis, done) {
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"rate","timeout":5,"nbRateUnits":nbUnit,"timeoutUnits":"seconds","rate":aLimit,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
var receivedMessagesStack = [];
var rate = 1000/aLimit;
var receiveTimestamp;
helperNode1.on("input", function(msg) {
if (receiveTimestamp) {
var elapse = process.hrtime(receiveTimestamp);
var receiveInterval = (elapse[0] * 1000) + ((elapse[1] / nanosToSeconds) * 1000);
receiveInterval.should.be.above(rate * 0.9);
}
receiveTimestamp = process.hrtime();
receivedMessagesStack.push(msg);
});
var possibleMaxMessageCount = Math.ceil(aLimit * (runtimeInMillis / 1000) + aLimit); // +aLimit as at the start of the 2nd period, we're allowing the 3rd burst
var i = 0;
delayNode1.receive({payload:i});
i++;
for (; i < possibleMaxMessageCount + 1; i++) {
setTimeout(function() {
delayNode1.receive({payload:i});
}, 2 * ((rate * i) / possibleMaxMessageCount) );
}
//we need to send a message delayed so that it doesn't get dropped
setTimeout(function() {
delayNode1.receive({payload:++i});
}, runtimeInMillis - 300); // should give enough time to squeeze another message in
setTimeout(function() {
try {
receivedMessagesStack.length.should.be.lessThan(possibleMaxMessageCount + 1);
receivedMessagesStack.length.should.be.greaterThan(2); // ensure that we receive more than 1st and last message
receivedMessagesStack[0].payload.should.be.exactly(0); // means we received the last message injected just before test termination
var foundAtLeastOneDrop = false;
for (var i = 0; i < receivedMessagesStack.length; i++) {
if (i > 0) {
if (receivedMessagesStack[i].payload - receivedMessagesStack[i - 1].payload > 1) {
foundAtLeastOneDrop = true;
}
}
}
foundAtLeastOneDrop.should.be.true();
done();
} catch (err) {
done(err);
}
}, runtimeInMillis);
});
}
it('limits the message rate to 1 per second, 4 seconds, with drop', function(done) {
this.timeout(6000);
dropRateLimitSECONDSTest(1, 1, 4000, done);
});
it('limits the message rate to 1 per 2 seconds, 4 seconds, with drop', function(done) {
this.timeout(6000);
dropRateLimitSECONDSTest(1, 2, 4500, done);
});
it('limits the message rate to 2 per second, 5 seconds, with drop', function(done) {
this.timeout(6000);
dropRateLimitSECONDSTest(2, 1, 5000, done);
});
/**
* Returns true if the actualTimeout is gracefully in between the timeoutFrom and timeoutTo
* values. Gracefully means that inBetween could actually mean smaller/greater values
* than the timeout range so long as it's within an actual grace percentage.
* @param timeoutFrom - The expected timeout range (low number)
* @param timeoutTo - The expected timeout range (high number)
* @param actualTimeout - The actual measured timeout value of test
* @param allowedGracePercent - The percentage of grace allowed
*/
function inBetweenDelays(timeoutFrom, timeoutTo, actualTimeout, allowedGracePercent) {
if (closeEnough(actualTimeout, timeoutFrom, allowedGracePercent)) {
return true;
} else if (closeEnough(actualTimeout, timeoutTo, allowedGracePercent)) {
return true;
} else if (timeoutFrom < actualTimeout && timeoutTo > actualTimeout) {
return true;
} else {
return false;
}
}
/**
* Runs a VARIABLE DELAY test, checks if the delay is in between the given timeout values
* @param aTimeoutFrom - the timeout quantity which is the minimal acceptable wait period
* @param aTimeoutTo - the timeout quantity which is the maximum acceptable wait period
* @param aTimeoutUnit - the unit of the timeout: milliseconds, seconds, minutes, hours, days
* @param delay - the variable delay: milliseconds
*/
function variableDelayTest(aTimeoutFrom, aTimeoutTo, aTimeoutUnit, delay, done) {
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"delayv","timeout":0.5,"timeoutUnits":"seconds","rate":"1","rateUnits":"second","randomFirst":aTimeoutFrom,"randomLast":aTimeoutTo,"randomUnits":aTimeoutUnit,"drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
var endTime = process.hrtime(startTime);
var runtimeNanos = ( (endTime[0] * nanosToSeconds) + endTime[1] );
var runtimeSeconds = runtimeNanos / nanosToSeconds;
var aTimeoutFromUnifiedToSeconds;
var aTimeoutToUnifiedToSeconds;
// calculating the timeout in seconds
if (aTimeoutUnit == TimeUnitEnum.MILLIS) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom / millisToSeconds;
aTimeoutToUnifiedToSeconds = aTimeoutTo / millisToSeconds;
} else if (aTimeoutUnit == TimeUnitEnum.SECONDS) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom;
aTimeoutToUnifiedToSeconds = aTimeoutTo;
} else if (aTimeoutUnit == TimeUnitEnum.MINUTES) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom * secondsToMinutes;
aTimeoutToUnifiedToSeconds = aTimeoutTo * secondsToMinutes;
} else if (aTimeoutUnit == TimeUnitEnum.HOURS) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom * secondsToHours;
aTimeoutToUnifiedToSeconds = aTimeoutTo * secondsToHours;
} else if (aTimeoutUnit == TimeUnitEnum.DAYS) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom * secondsToDays;
aTimeoutToUnifiedToSeconds = aTimeoutTo * secondsToDays;
}
if (inBetweenDelays(aTimeoutFromUnifiedToSeconds, aTimeoutToUnifiedToSeconds, runtimeSeconds, GRACE_PERCENTAGE)) {
done();
} else {
try {
should.fail(null, null, "Delayed runtime seconds " + runtimeSeconds + " was not \"in between enough\" enough to expected values of: " + aTimeoutFromUnifiedToSeconds + " and " + aTimeoutToUnifiedToSeconds);
} catch (err) {
done(err);
}
}
} catch(err) {
done(err);
}
});
var startTime = process.hrtime();
delayNode1.receive({payload:"delayMe", delay:delay});
});
}
it('variable delay set by msg.delay the message in milliseconds', function(done) {
variableDelayTest("200", "300", "milliseconds", 250, done);
});
it('variable delay is the default if msg.delay not specified', function(done) {
variableDelayTest("450", "550", "milliseconds", null, done);
});
it('variable delay is zero if msg.delay is zero', function(done) {
variableDelayTest("0", "20", "milliseconds", 0, done);
});
it('variable delay is zero if msg.delay is negative', function(done) {
variableDelayTest("0", "20", "milliseconds", -250, done);
});
/**
* Runs a RANDOM DELAY test, checks if the delay is in between the given timeout values
* @param aTimeoutFrom - the timeout quantity which is the minimal acceptable wait period
* @param aTimeoutTo - the timeout quantity which is the maximum acceptable wait period
* @param aTimeoutUnit - the unit of the timeout: milliseconds, seconds, minutes, hours, days
*/
function randomDelayTest(aTimeoutFrom, aTimeoutTo, aTimeoutUnit, done) {
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"random","timeout":5,"timeoutUnits":"seconds","rate":"1","rateUnits":"second","randomFirst":aTimeoutFrom,"randomLast":aTimeoutTo,"randomUnits":aTimeoutUnit,"drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
var endTime = process.hrtime(startTime);
var runtimeNanos = ( (endTime[0] * nanosToSeconds) + endTime[1] );
var runtimeSeconds = runtimeNanos / nanosToSeconds;
var aTimeoutFromUnifiedToSeconds;
var aTimeoutToUnifiedToSeconds;
// calculating the timeout in seconds
if (aTimeoutUnit == TimeUnitEnum.MILLIS) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom / millisToSeconds;
aTimeoutToUnifiedToSeconds = aTimeoutTo / millisToSeconds;
} else if (aTimeoutUnit == TimeUnitEnum.SECONDS) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom;
aTimeoutToUnifiedToSeconds = aTimeoutTo;
} else if (aTimeoutUnit == TimeUnitEnum.MINUTES) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom * secondsToMinutes;
aTimeoutToUnifiedToSeconds = aTimeoutTo * secondsToMinutes;
} else if (aTimeoutUnit == TimeUnitEnum.HOURS) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom * secondsToHours;
aTimeoutToUnifiedToSeconds = aTimeoutTo * secondsToHours;
} else if (aTimeoutUnit == TimeUnitEnum.DAYS) {
aTimeoutFromUnifiedToSeconds = aTimeoutFrom * secondsToDays;
aTimeoutToUnifiedToSeconds = aTimeoutTo * secondsToDays;
}
if (inBetweenDelays(aTimeoutFromUnifiedToSeconds, aTimeoutToUnifiedToSeconds, runtimeSeconds, GRACE_PERCENTAGE)) {
done();
} else {
try {
should.fail(null, null, "Delayed runtime seconds " + runtimeSeconds + " was not \"in between enough\" enough to expected values of: " + aTimeoutFromUnifiedToSeconds + " and " + aTimeoutToUnifiedToSeconds);
} catch (err) {
done(err);
}
}
} catch(err) {
done(err);
}
});
var startTime = process.hrtime();
delayNode1.receive({payload:"delayMe"});
});
}
it('randomly delays the message in seconds', function(done) {
randomDelayTest(0.4, 0.8, "seconds", done);
});
it('randomly delays the message in milliseconds', function(done) {
randomDelayTest("400", "800", "milliseconds", done);
});
it('randomly delays the message in minutes', function(done) {
randomDelayTest(0.0066, 0.0133, "minutes", done);
});
it('delays the message in hours', function(done) {
randomDelayTest(0.000111111, 0.000222222, "hours", done);
});
it('delays the message in days', function(done) {
randomDelayTest(0.0000046296, 0.0000092593, "days", done);
});
it('handles delay queue', function(done) {
this.timeout(2000);
var flow = [{id:"delayNode1", type :"delay","name":"delayNode","nbRateUnits":"1","pauseType":"queue","timeout":1,"timeoutUnits":"seconds","rate":4,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
var t = Date.now();
helperNode1.on("input", function(msg) {
msg.should.have.a.property('payload');
msg.should.have.a.property('topic');
try {
if (msg.topic === "_none_") {
msg.payload.should.equal(2);
(Date.now() - t).should.be.approximately(500,200);
}
else if (msg.topic === "A") {
msg.payload.should.equal(4);
(Date.now() - t).should.be.approximately(750,200);
}
else {
msg.topic.should.equal("B");
msg.payload.should.equal(1);
(Date.now() - t).should.be.approximately(1000,200);
done();
}
} catch(e) {
done(e);
}
});
setTimeout(function() {
// send test messages
delayNode1.receive({payload:1}); // send something with blank topic
delayNode1.receive({payload:1,topic:"A"}); // and something with a fixed topic
delayNode1.receive({payload:1,topic:"B"}); // and something else with a fixed topic (3rd tick)
delayNode1.receive({payload:2,topic:"A"}); // these should replace them in queue
delayNode1.receive({payload:3,topic:"A"}); // ditto
delayNode1.receive({payload:2}); // so only this should get out on first tick
delayNode1.receive({payload:4,topic:"A"}); // and this one on second tick
}, 275); // wait one tick beofre starting.. (to test no messages in queue path.)
});
});
it('handles timed queue', function(done) {
this.timeout(2000);
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"timed","timeout":1,"timeoutUnits":"seconds","rate":2,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
var t = Date.now();
helperNode1.on("input", function(msg) {
msg.should.have.a.property('payload');
msg.should.have.a.property('topic');
try {
if (msg.topic === "_none_") {
msg.payload.should.equal(2);
(Date.now() - t).should.be.approximately(500,200);
}
else if (msg.topic === "A") {
msg.payload.should.equal(4);
(Date.now() - t).should.be.approximately(500,200);
}
else {
msg.topic.should.equal("B");
msg.payload.should.equal(1);
(Date.now() - t).should.be.approximately(500,200);
done();
}
} catch(e) {
done(e);
}
});
// send test messages
delayNode1.receive({payload:1}); // send something with blank topic
delayNode1.receive({payload:1,topic:"A"}); // and something with a fixed topic
delayNode1.receive({payload:1,topic:"B"}); // and something else with a fixed topic
delayNode1.receive({payload:2,topic:"A"}); // these should replace them in queue
delayNode1.receive({payload:3,topic:"A"}); // ditto
delayNode1.receive({payload:2}); // so all should go on first tick
delayNode1.receive({payload:4,topic:"A"}); // and nothing on second
});
});
it('can flush delay queue', function(done) {
this.timeout(2000);
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"delay","timeout":1,"timeoutUnits":"seconds","rate":2,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
var t = Date.now();
var c = 0;
helperNode1.on("input", function(msg) {
msg.should.have.a.property('payload');
msg.should.have.a.property('topic');
try {
if (msg.topic === "foo") {
msg.payload.should.equal(1);
(Date.now() - t).should.be.approximately(0,100);
c = c + 1;
}
else {
if (msg.topic === "bar") {
msg.payload.should.equal(1);
(Date.now() - t).should.be.approximately(0,100);
c = c + 1;
}
}
if (c === 5) { done(); }
} catch(e) {
done(e);
}
});
// send test messages
delayNode1.receive({payload:1,topic:"foo"}); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({flush:true}); }); // reset the queue
});
});
it('can reset delay queue', function(done) {
this.timeout(2000);
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"delay","timeout":1,"timeoutUnits":"seconds","rate":2,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
var t = Date.now();
var c = 0;
helperNode1.on("input", function(msg) {
c = c + 1;
});
setTimeout( function() {
if (c === 0) { done(); }
}, 700);
// send test messages
delayNode1.receive({payload:1,topic:"foo"}); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({reset:true}); }); // reset the queue
});
});
it('can flush rate limit queue', function(done) {
this.timeout(2000);
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"rate","timeout":1,"timeoutUnits":"seconds","rate":2,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
var t = Date.now();
var c = 0;
helperNode1.on("input", function(msg) {
msg.should.have.a.property('payload');
msg.should.have.a.property('topic');
try {
if (msg.topic === "foo") {
msg.payload.should.equal(1);
(Date.now() - t).should.be.approximately(0,100);
c = c + 1;
}
else {
if (msg.topic === "bar") {
msg.payload.should.equal(1);
(Date.now() - t).should.be.approximately(0,100);
c = c + 1;
}
}
if (c === 5) { done(); }
} catch(e) {
done(e);
}
});
// send test messages
delayNode1.receive({payload:1,topic:"foo"}); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({flush:true}); }); // reset the queue
});
});
it('can reset rate limit queue', function(done) {
this.timeout(2000);
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"rate","timeout":1,"timeoutUnits":"seconds","rate":2,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(delayNode, flow, function() {
var delayNode1 = helper.getNode("delayNode1");
var helperNode1 = helper.getNode("helperNode1");
var t = Date.now();
var c = 0;
helperNode1.on("input", function(msg) {
msg.should.have.a.property('payload');
msg.should.have.a.property('topic');
try {
if (msg.topic === "foo") {
msg.payload.should.equal(1);
(Date.now() - t).should.be.approximately(0,100);
c = c + 1;
}
} catch(e) {
done(e);
}
});
setTimeout( function() {
if (c === 1) { done(); }
}, 700);
// send test messages
delayNode1.receive({payload:1,topic:"foo"}); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({payload:1,topic:"bar"}); } ); // send something with blank topic
setImmediate( function() { delayNode1.receive({reset:true}); }); // reset the queue
});
});
});

View File

@@ -0,0 +1,974 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var helper = require("node-red-node-test-helper");
var triggerNode = require("nr-test-utils").require("@node-red/nodes/core/function/89-trigger.js");
var Context = require("nr-test-utils").require("@node-red/runtime/lib/nodes/context");
var RED = require("nr-test-utils").require("node-red/lib/red");
describe('trigger node', function() {
beforeEach(function(done) {
helper.startServer(done);
});
function initContext(done) {
Context.init({
contextStorage: {
memory0: {
module: "memory"
},
memory1: {
module: "memory"
},
memory2: {
module: "memory"
}
}
});
Context.load().then(function () {
done();
});
}
afterEach(function(done) {
helper.unload().then(function () {
return Context.clean({allNodes: {}});
}).then(function () {
return Context.close();
}).then(function () {
helper.stopServer(done);
});
});
it("should be loaded with correct defaults", function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", "wires":[[]]}];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'triggerNode');
n1.should.have.property('op1', '1');
n1.should.have.property('op2', '0');
n1.should.have.property('op1type', 'str');
n1.should.have.property('op2type', 'str');
n1.should.have.property('extend', "false");
n1.should.have.property('units', 'ms');
n1.should.have.property('duration', 250);
done();
});
});
it("should be able to set delay in seconds", function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", units:"s", duration:"1", "wires":[[]]}];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('duration', 1000);
done();
});
});
it("should be able to set delay in minutes", function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", units:"min", duration:"1", "wires":[[]]}];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('duration', 60000);
done();
});
});
it("should be able to set delay in hours", function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", units:"hr", duration:"1", "wires":[[]]}];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('duration', 3600000);
done();
});
});
function basicTest(type, val, rval) {
it('should output 1st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:val, op1type:type, op2:"", op2type:"null", duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
process.env[val] = rval;
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
if (rval) {
msg.should.have.property("payload");
should.deepEqual(msg.payload, rval);
}
else {
msg.should.have.property("payload", val);
}
delete process.env[val];
done();
}
catch(err) { done(err); }
});
n1.emit("input", {payload:null});
});
});
it('should output 2st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"str", op2:val, op2type:type, duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
process.env[val] = rval;
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.property("payload", "foo");
c++;
}
else {
if (rval) {
msg.should.have.property("payload");
should.deepEqual(msg.payload, rval);
}
else {
msg.should.have.property("payload", val);
}
delete process.env[val];
done();
}
}
catch(err) { done(err); }
});
n1.emit("input", {payload:null});
});
});
}
basicTest("num", 10);
basicTest("str", "10");
basicTest("bool", true);
var val_json = '{ "x":"vx", "y":"vy", "z":"vz" }';
basicTest("json", val_json, JSON.parse(val_json));
var val_buf = "[1,2,3,4,5]";
basicTest("bin", val_buf, Buffer.from(JSON.parse(val_buf)));
basicTest("env", "NR-TEST", "env-val");
it('should output 1 then 0 when triggered (default)', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", '1');
c+=1;
}
else {
msg.should.have.a.property("payload", '0');
done();
}
}
catch(err) { done(err); }
});
n1.emit("input", {payload:null});
});
});
it('should ignore any other inputs while triggered if extend is false', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", duration:"50",wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
var errored = false;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", '1');
}
else {
msg.should.have.a.property("payload", '0');
}
c+=1;
}catch(err) {
errored = true;
done(err);
}
});
setTimeout( function() {
if (!errored) {
try {
c.should.equal(2);
done();
} catch(err) {
done(err);
}
}
},100);
n1.emit("input", {payload:null});
setTimeout( function() {
n1.emit("input", {payload:null});
},10);
setTimeout( function() {
n1.emit("input", {payload:null});
},30);
});
});
it('should handle true and false as strings and delay of 0', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"true",op1type:"val",op2:"false",op2type:"val",duration:"30", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", true);
c+=1;
}
else {
msg.should.have.a.property("payload", false);
done();
}
} catch(err) {
done(err);
}
});
n1.emit("input", {payload:null});
});
});
it('should handle multiple topics as one if not asked to handle', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"all", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
if (c === 1) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("topic", "A");
}
else if (c === 2) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("topic", "A");
done();
}
} catch(err) {
done(err);
}
});
n1.emit("input", {payload:1,topic:"A"});
n1.emit("input", {payload:2,topic:"B"});
n1.emit("input", {payload:3,topic:"C"});
});
});
it('should handle multiple topics individually if asked to do so', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
if (c === 1) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("topic", "A");
}
else if (c === 2) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("topic", "B");
}
else if (c === 3) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("topic", "C");
}
else if (c === 4) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("topic", "A");
}
else if (c === 5) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("topic", "B");
}
else if (c === 6) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("topic", "C");
done();
}
} catch(err) {
done(err);
}
});
n1.emit("input", {payload:1,topic:"A"});
n1.emit("input", {payload:2,topic:"B"});
n1.emit("input", {payload:3,topic:"C"});
});
});
it('should handle multiple topics individually, and extend one, if asked to do so', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", extend:"true", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
if (c === 1) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("topic", "A");
}
else if (c === 2) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("topic", "B");
}
else if (c === 3) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("topic", "C");
}
else if (c === 4) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("topic", "A");
}
else if (c === 5) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("topic", "C");
}
else if (c === 6) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("topic", "B");
done();
}
} catch(err) {
done(err);
}
});
n1.emit("input", {payload:1,topic:"A"});
n1.emit("input", {payload:2,topic:"B"});
n1.emit("input", {payload:3,topic:"C"});
setTimeout( function() { n1.emit("input", {payload:2,topic:"B"})}, 20 );
});
});
it('should be able to return things from flow and global context variables', function(done) {
var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } }
);
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"flow", op2:"bar", op2type:"global", duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", "foo");
c+=1;
}
else {
msg.should.have.a.property("payload", "bar");
RED.util.evaluateNodeProperty.restore();
done();
}
}
catch(err) { RED.util.evaluateNodeProperty.restore(); done(err); }
});
n1.emit("input", {payload:null});
});
});
it('should be able to return things from persistable flow and global context variables', function (done) {
var flow = [{"id": "n1", "type": "trigger", "name": "triggerNode", "op1": "#:(memory1)::foo", "op1type": "flow",
"op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" },
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function (msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", "foo");
c += 1;
} else {
msg.should.have.a.property("payload", "bar");
done();
}
} catch (err) {
done(err);
}
});
var context = n1.context();
var flow = context.flow;
var global = context.global;
flow.set("foo", "foo", "memory1", function (err) {
global.set("bar", "bar", "memory1", function (err) {
n1.emit("input", { payload: null });
});
});
});
});
});
it('should be able to return things from multiple persistable global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "global",
"op2": "#:(memory2)::val", "op2type": "global"
},
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function (msg) {
try {
if (count === 0) {
msg.should.have.a.property("payload", "foo");
}
else {
msg.should.have.a.property("payload", "bar");
}
count++;
if (count === 1) {
done();
}
}
catch (err) {
done(err);
}
});
var global = n1.context().global;
global.set("val", "foo", "memory1", function (err) {
global.set("val", "bar", "memory2", function (err) {
n1.emit("input", { payload: null });
});
});
});
});
});
it('should be able to return things from multiple persistable flow context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "flow"
},
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function (msg) {
try {
if (count === 0) {
msg.should.have.a.property("payload", "foo");
}
else {
msg.should.have.a.property("payload", "bar");
}
count++;
if (count === 1) {
done();
}
}
catch (err) {
done(err);
}
});
var flow = n1.context().flow;
flow.set("val", "foo", "memory1", function (err) {
flow.set("val", "bar", "memory2", function (err) {
n1.emit("input", { payload: null });
});
});
});
});
});
it('should be able to return things from multiple persistable flow & global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "global"
},
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function (msg) {
try {
if (count === 0) {
msg.should.have.a.property("payload", "foo");
}
else {
msg.should.have.a.property("payload", "bar");
}
count++;
if (count === 1) {
done();
}
}
catch (err) {
done(err);
}
});
var context = n1.context();
var flow = context.flow;
var global = context.flow;
flow.set("val", "foo", "memory1", function (err) {
global.set("val", "bar", "memory2", function (err) {
n1.emit("input", { payload: null });
});
});
});
});
});
it('should be able to not output anything on first trigger', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"nul", op1:"true",op2:"false",op2type:"val",duration:"30", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.a.property("payload", false);
done();
} catch(err) {
done(err);
}
});
n1.emit("input", {payload:null});
});
});
it('should be able to not output anything on second edge', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op2type:"nul", op1:"true",op1type:"val", op2:"false", duration:"30", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
msg.should.have.a.property("payload", true);
c += 1;
}
catch(err) { done(err); }
});
setTimeout( function() {
c.should.equal(1); // should only have had one output.
done();
},90);
n1.emit("input", {payload:null});
});
});
it('should be able to reset correctly having not output anything on second edge', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op2type:"nul", op1:"true",op1type:"val", op2:"false", duration:"35", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
msg.should.have.a.property("payload", true);
c += 1;
}
catch(err) { done(err); }
});
setTimeout( function() {
c.should.equal(3); // should only have had one output.
done();
},300);
n1.emit("input", {payload:1});
setTimeout( function() {
n1.emit("input", {payload:2});
},100);
setTimeout( function() {
n1.emit("input", {payload:3});
},200);
});
});
it('should be able to extend the delay', function(done) {
this.timeout(5000); // add extra time for flake
var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } }
);
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"flow", op1:"foo", op2:"bar", op2type:"global", duration:"100", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", "foo");
c += 1;
}
else {
msg.should.have.a.property("payload", "bar");
//console.log(Date.now() - ss);
(Date.now() - ss).should.be.greaterThan(149);
spy.restore();
done();
}
}
catch(err) { spy.restore(); done(err); }
});
var ss = Date.now();
n1.emit("input", {payload:"Hello"});
setTimeout( function() {
n1.emit("input", {payload:null});
},50);
});
});
it('should be able to extend the delay (but with no 2nd output)', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op2type:"nul", op1:"false", op2:"true", duration:"200", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", "Hello");
c += 1;
}
else {
msg.should.have.a.property("payload", "World");
(Date.now() - ss).should.be.greaterThan(300);
done();
}
} catch(err) {
console.log(err);
done(err);
}
});
var ss = Date.now();
n1.emit("input", {payload:"Hello"});
setTimeout( function() {
n1.emit("input", {payload:"Error"});
},50);
setTimeout( function() {
n1.emit("input", {payload:"Error"});
},100);
setTimeout( function() {
n1.emit("input", {payload:"World"});
},330);
});
});
it('should be able to extend the delay and output the most recent payload', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"nul", op2type:"payl", op1:"false", op2:"true", duration:"60", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
msg.should.have.a.property("payload", "World");
(Date.now() - ss).should.be.greaterThan(120);
done();
}
catch(err) { done(err); }
});
var ss = Date.now();
n1.emit("input", {payload:"Hello"});
setTimeout( function() {
n1.emit("input", {payload:"Goodbye"});
},40);
setTimeout( function() {
n1.emit("input", {payload:"World"});
},80);
});
});
it('should be able output the 2nd payload', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"false", op1type:"nul", op2type:"payl", op1:"false", op2:"true", duration:"50", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", "Goodbye");
c += 1;
}
else {
msg.should.have.a.property("payload", "World");
(Date.now() - ss).should.be.greaterThan(70);
done();
}
}
catch(err) { done(err); }
});
var ss = Date.now();
n1.emit("input", {payload:"Hello"});
setTimeout( function() {
n1.emit("input", {payload:"Goodbye"});
},20);
setTimeout( function() {
n1.emit("input", {payload:"World"});
},80);
});
});
it('should be able to apply mustache templates to payloads', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"{{payload}}", op2:"{{topic}}", duration:"50", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", "Hello");
c+=1;
}
else {
msg.should.have.a.property("payload", "World");
done();
}
}
catch(err) { done(err); }
});
n1.emit("input", {payload:"Hello",topic:"World"});
});
});
it('should handle string null as null', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"pay", op1:"null", op2:"null", duration:"40", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", null);
c+=1;
}
else {
msg.should.have.a.property("payload", "World");
done();
}
}
catch(err) { done(err); }
});
n1.emit("input", {payload:"World"});
});
});
it('should handle string null as null on op2', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"null", op2:"null", duration:"40", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", null);
c+=1;
}
else {
msg.should.have.a.property("payload", null);
done();
}
}
catch(err) { done(err); }
});
n1.emit("input", {payload:"null"});
});
});
it('should be able to set infinite timeout, and clear timeout', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", duration:"0", extend: false, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
msg.should.have.a.property("payload", "1");
}
catch(err) { done(err); }
});
setTimeout( function() {
if (c === 2) { done(); }
else {
done(new Error("Too many messages received"));
}
},20);
n1.emit("input", {payload:null}); // trigger
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {reset:true}); // clear the blockage
n1.emit("input", {payload:null}); // trigger
});
});
it('should be able to set infinite timeout, and clear timeout by message', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"boo", duration:"0", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
msg.should.have.a.property("payload", "1");
}
catch(err) { done(err); }
});
setTimeout( function() {
if (c === 2) { done(); }
else {
done(new Error("Too many messages received"));
}
},20);
n1.emit("input", {payload:null}); // trigger
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:"foo"}); // don't clear the blockage
n1.emit("input", {payload:"boo"}); // clear the blockage
n1.emit("input", {payload:null}); // trigger
});
});
it('should be able to set infinite timeout, and clear timeout by boolean true', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"true", duration:"0", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
msg.should.have.a.property("payload", "1");
}
catch(err) { done(err); }
});
setTimeout( function() {
if (c === 2) { done(); }
else {
done(new Error("Too many messages received"));
}
},20);
n1.emit("input", {payload:null}); // trigger
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:false}); // don't clear the blockage
n1.emit("input", {payload:true}); // clear the blockage
n1.emit("input", {payload:null}); // trigger
});
});
it('should be able to set infinite timeout, and clear timeout by boolean false', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"false", duration:"0", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
msg.should.have.a.property("payload", "1");
}
catch(err) { done(err); }
});
setTimeout( function() {
if (c === 2) { done(); }
else {
done(new Error("Too many messages received"));
}
},20);
n1.emit("input", {payload:null}); // trigger
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:"foo"}); // don't clear the blockage
n1.emit("input", {payload:false}); // clear the blockage
n1.emit("input", {payload:null}); // trigger
});
});
it('should be able to set a repeat, and clear loop by reset', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"boo", op1:"", op1type:"pay", duration:-25, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
c += 1;
try {
msg.should.have.property('payload','foo');
msg.payload = "bar"; // try to provoke pass by reference error
}
catch(err) { done(err); }
});
n1.emit("input", {payload:"foo"}); // trigger
n1.emit("input", {payload:"foo"}); // trigger
setTimeout( function() {
n1.emit("input", {reset:true}); // reset
},90);
setTimeout( function() {
c.should.within(2,5); // should send foo between 2 and 5 times.
done();
},180);
});
});
});

View File

@@ -0,0 +1,919 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var helper = require("node-red-node-test-helper");
var execNode = require("nr-test-utils").require("@node-red/nodes/core/function/90-exec.js");
var osType = require("os").type();
var child_process = require('child_process');
describe('exec node', function() {
beforeEach(function(done) {
helper.startServer(done);
});
afterEach(function(done) {
helper.unload().then(function() {
helper.stopServer(done);
});
});
it('should be loaded with any defaults', function(done) {
var flow = [{id:"n1", type:"exec", name: "exec1"}];
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property("name", "exec1");
n1.should.have.property("cmd", "");
n1.should.have.property("append", "");
n1.should.have.property("addpay",true);
n1.should.have.property("timer",0);
n1.should.have.property("oldrc","false");
done();
});
});
describe('calling exec', function() {
it('should exec a simple command', function(done) {
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo", addpay:false, append:"", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
var spy = sinon.stub(child_process, 'exec',
function(arg1, arg2, arg3, arg4) {
// arg3(error,stdout,stderr);
arg3(null,arg1,arg1.toUpperCase());
});
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null,null];
var completeTest = function() {
received = received + 1;
if (received < 3) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("echo");
msg.should.have.property("rc");
msg.rc.should.have.property("code",0);
msg = messages[1];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("ECHO");
msg.should.have.property("rc");
msg.rc.should.have.property("code",0);
msg = messages[2];
msg.should.have.property("payload");
msg.payload.should.have.property("code",0);
child_process.exec.restore();
done();
}
catch(err) {
child_process.exec.restore();
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n3.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[2] = msg;
completeTest();
});
n1.receive({payload:"and"});
});
});
it('should exec a simple command with extra parameters', function(done) {
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo", addpay:true, append:"more", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
var spy = sinon.stub(child_process, 'exec',
function(arg1, arg2, arg3, arg4) {
//console.log(arg1);
// arg3(error,stdout,stderr);
arg3(null,arg1,arg1.toUpperCase());
});
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null];
var completeTest = function() {
received++;
if (received < 2) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("echo and more");
msg.should.have.property("rc");
msg.rc.should.have.property("code",0);
msg = messages[1];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("ECHO AND MORE");
msg.should.have.property("rc");
msg.rc.should.have.property("code",0);
child_process.exec.restore();
done();
}
catch(err) {
child_process.exec.restore();
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n3.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n1.receive({payload:"and"});
});
});
it('should be able to return a binary buffer', function(done) {
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo", addpay:true, append:"more", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
var spy = sinon.stub(child_process, 'exec',
function(arg1, arg2, arg3, arg4) {
//console.log(arg1);
// arg3(error,stdout,stderr);
arg3("error",Buffer.from([0x01,0x02,0x03,0x88]),Buffer.from([0x01,0x02,0x03,0x88]));
});
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n2.on("input", function(msg) {
//console.log("n2",msg);
try {
msg.should.have.property("payload");
Buffer.isBuffer(msg.payload).should.be.true();
msg.payload.length.should.equal(4);
child_process.exec.restore();
done();
} catch(err) {
child_process.exec.restore();
done(err);
}
});
n1.receive({});
});
});
it('should be able to timeout a long running command', function(done) {
var flow;
if (osType === "Windows_NT") {
// Although Windows timeout command is equivalent to sleep, this cannot be used because it promptly outputs a message.
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping", addpay:false, append:"192.0.2.0 -n 1 -w 1000 > NUL", timer:"0.3", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
}
else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"0.3", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n4.on("input", function(msg) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("signal","SIGTERM");
done();
}
catch(err) { done(err); }
});
n1.receive({});
});
});
it('should be able to kill a long running command', function(done) {
var flow;
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping", addpay:false, append:"192.0.2.0 -n 1 -w 1000 > NUL", timer:"2", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
}
else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"2", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n2.on("input", function(msg) {
try {
msg.should.have.property("rc");
msg.rc.should.have.property("code",null);
msg.rc.should.have.property("signal","SIGTERM");
} catch(err) { done(err); }
});
n4.on("input", function(msg) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("signal","SIGTERM");
done();
}
catch(err) { done(err); }
});
setTimeout(function() {
n1.receive({kill:""});
},150);
n1.receive({});
});
});
it('should be able to kill a long running command - SIGINT', function(done) {
var flow;
var sig = "SIGINT";
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping", addpay:false, append:"192.0.2.0 -n 1 -w 1000 > NUL", timer:"2", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"2", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n2.on("input", function(msg) {
try {
msg.should.have.property("rc");
msg.rc.should.have.property("code",null);
msg.rc.should.have.property("signal","SIGINT");
} catch(err) { done(err); }
});
n4.on("input", function(msg) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("signal",sig);
done();
} catch(err) { done(err); }
});
setTimeout(function() {
n1.receive({kill:"SIGINT"});
},150);
n1.receive({});
});
});
it('should return the rc for a failing command', function(done) {
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"error", addpay:false, append:"", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
var spy = sinon.stub(child_process, 'exec',
function(arg1, arg2, arg3, arg4) {
//console.log(arg1);
// arg3(error,stdout,stderr);
arg3({code: 1},arg1,arg1.toUpperCase());
});
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null,null];
var completeTest = function() {
received++;
if (received < 3) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("error");
msg.should.have.property("rc");
msg.rc.should.have.property("code",1);
msg.rc.should.have.property("message",undefined);
msg = messages[1];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("ERROR");
msg = messages[2];
msg.should.have.property("payload");
msg.payload.should.have.property("code",1);
child_process.exec.restore();
done();
}
catch(err) {
child_process.exec.restore();
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n3.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[2] = msg;
completeTest();
});
n1.receive({payload:"and"});
});
});
it('should preserve existing properties on msg object', function(done) {
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo", addpay:false, append:"", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
var spy = sinon.stub(child_process, 'exec',
function(arg1, arg2, arg3, arg4) {
// arg3(error,stdout,stderr);
arg3(null,arg1,arg1.toUpperCase());
});
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null,null];
var completeTest = function() {
received = received + 1;
if (received < 3) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("echo");
msg.should.have.property("rc");
msg.rc.should.have.property("code",0);
msg.should.have.property("foo","bar");
msg = messages[1];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("ECHO");
msg.should.have.property("rc");
msg.rc.should.have.property("code",0);
msg.should.have.property("foo","bar");
msg = messages[2];
msg.should.have.property("payload");
msg.payload.should.have.property("code",0);
msg.should.have.property("foo","bar");
child_process.exec.restore();
done();
}
catch(err) {
child_process.exec.restore();
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n3.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[2] = msg;
completeTest();
});
n1.receive({payload:"and", foo:"bar"});
});
});
it('should preserve existing properties on msg object for a failing command', function(done) {
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"error", addpay:false, append:"", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
var spy = sinon.stub(child_process, 'exec',
function(arg1, arg2, arg3, arg4) {
// arg3(error,stdout,stderr);
arg3({code: 1},arg1,arg1.toUpperCase());
});
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null,null];
var completeTest = function() {
received++;
if (received < 3) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("error");
msg.should.have.property("rc");
msg.rc.should.have.property("code",1);
msg.rc.should.have.property("message",undefined);
msg.should.have.property("foo",null);
msg = messages[1];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal("ERROR");
msg.should.have.property("foo",null);
msg = messages[2];
msg.should.have.property("payload");
msg.payload.should.have.property("code",1);
msg.should.have.property("foo",null);
child_process.exec.restore();
done();
}
catch(err) {
child_process.exec.restore();
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n3.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[2] = msg;
completeTest();
});
n1.receive({payload:"and", foo:null});
});
});
});
describe('calling spawn', function() {
it('should spawn a simple command', function(done) {
var flow;
var expected;
if (osType === "Windows_NT") {
// Need to use cmd to spawn a process because Windows echo command is a built-in command and cannot be spawned.
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"cmd /C echo", addpay:true, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "hello world\r\n";
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo", addpay:true, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "hello world\n";
}
var events = require('events');
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n2.on("input", function(msg) {
//console.log(msg);
try {
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal(expected);
done();
}
catch(err) { done(err); }
});
n1.receive({payload:"hello world"});
});
});
it('should spawn a simple command with a non string payload parameter', function(done) {
var flow;
var expected;
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"cmd /C echo", addpay:true, append:" deg C", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "12345 deg C\r\n";
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo", addpay:true, append:" deg C", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "12345 deg C\n";
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n2.on("input", function(msg) {
//console.log(msg);
try {
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal(expected);
done();
}
catch(err) { done(err); }
});
n1.receive({payload:12345});
});
});
it('should spawn a simple command and return binary buffer', function(done) {
var flow;
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"cmd /C echo", addpay:true, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo", addpay:true, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n2.on("input", function(msg) {
try {
msg.should.have.property("payload");
Buffer.isBuffer(msg.payload).should.be.true();
if (osType === "Windows_NT") {
msg.payload.length.should.equalOneOf(6,8);
} else {
msg.payload.length.should.equal(7);
}
done();
}
catch(err) { done(err); }
});
n1.receive({payload:Buffer.from([0x01,0x02,0x03,0x88])});
});
});
it('should work if passed multiple words to spawn command', function(done) {
var flow;
var expected;
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"cmd /C echo this now works", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "this now works\r\n";
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo this now works", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "this now works\n";
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null];
var completeTest = function() {
received++;
if (received < 2) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal(expected);
msg = messages[1];
msg.should.have.property("payload");
should.exist(msg.payload);
msg.payload.should.have.property("code",0);
done();
}
catch(err) {
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n1.receive({payload:null,fred:123});
});
});
it('should return an error for a bad command', function(done) {
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"madeupcommandshouldfail", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n4.on("input", function(msg) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("code");
msg.payload.code.should.be.below(0);
done();
}
catch(err) { done(err); }
});
n1.receive({payload:null});
});
});
it('should return an error for a failing command', function(done) {
var flow;
var expected;
var expectedFound = false;
if (osType === "Windows_NT") {
// Cannot use mkdir because Windows mkdir command automatically creates non-existent directories.
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping /foo/bar/doo/dah", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "IP address must be specified.";
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"mkdir /foo/bar/doo/dah", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = ' directory';
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n3.on("input", function(msg) {
try {
msg.should.have.property("payload");
msg.payload.should.be.a.String();
if (msg.payload.indexOf(expected) >= 0) {
// The error text on the stderr stream might get sent in more than one piece.
// We only need to know that it occurred before the return code is sent,
// as checked below in node n4.
expectedFound = true;
}
}
catch(err) { done(err); }
});
n4.on("input", function(msg) {
try {
expectedFound.should.be.true;
msg.should.have.property("payload");
msg.payload.should.have.property("code",1);
done();
}
catch(err) { done(err); }
});
n1.receive({payload:null});
});
});
it('should be able to timeout a long running command', function(done) {
var flow;
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping", addpay:false, append:"192.0.2.0 -n 1 -w 1000", timer:"0.3", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"0.3", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n4.on("input", function(msg) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("code",null);
msg.payload.should.have.property("signal","SIGTERM");
done();
}
catch(err) { done(err); }
});
n1.receive({});
});
});
it('should be able to kill a long running command', function(done) {
var flow;
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping", addpay:false, append:"192.0.2.0 -n 1 -w 1000 > NUL", timer:"2", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"2", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n4.on("input", function(msg) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("signal","SIGTERM");
done();
}
catch(err) { done(err); }
});
setTimeout(function() {
n1.receive({kill:""});
},150);
n1.receive({});
});
});
it('should be able to kill a long running command - SIGINT', function(done) {
var flow;
var sig = "SIGINT";
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping", addpay:false, append:"192.0.2.0 -n 1 -w 1000 > NUL", timer:"2", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"2", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
n4.on("input", function(msg) {
try {
msg.should.have.property("payload");
msg.payload.should.have.property("signal",sig);
done();
}
catch(err) { done(err); }
});
setTimeout(function() {
n1.receive({kill:"SIGINT"});
},150);
n1.receive({});
});
});
it('should preserve existing properties on msg object', function(done) {
var flow;
var expected;
if (osType === "Windows_NT") {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"cmd /C echo this now works", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "this now works\r\n";
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"echo this now works", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "this now works\n";
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null];
var completeTest = function() {
received++;
if (received < 2) {
return;
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.payload.should.equal(expected);
msg.should.have.property("foo",123);
msg = messages[1];
msg.should.have.property("payload");
should.exist(msg.payload);
msg.payload.should.have.property("code",0);
msg.should.have.property("foo",123);
done();
}
catch(err) {
done(err);
}
};
n2.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n1.receive({payload:null,foo:123});
});
});
it('should preserve existing properties on msg object for a failing command', function(done) {
var flow;
var expected;
if (osType === "Windows_NT") {
// Cannot use mkdir because Windows mkdir command automatically creates non-existent directories.
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"ping /foo/bar/doo/dah", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = "IP address must be specified.";
} else {
flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"mkdir /foo/bar/doo/dah", addpay:false, append:"", useSpawn:"true", oldrc:"false"},
{id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
expected = ' directory';
}
helper.load(execNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var received = 0;
var messages = [null,null];
var completeTest = function() {
if (messages[0] === null || messages[1] === null) {
// We have not yet had responses on both ports.
return
}
try {
var msg = messages[0];
msg.should.have.property("payload");
msg.payload.should.be.a.String();
msg.should.have.property("foo","baz");
msg = messages[1];
msg.should.have.property("payload");
msg.payload.should.have.property("code",1);
msg.should.have.property("foo","baz");
done();
}
catch(err) {
done(err);
}
};
n3.on("input", function(msg) {
messages[0] = msg;
completeTest();
});
n4.on("input", function(msg) {
messages[1] = msg;
completeTest();
});
n1.receive({payload:null,foo:"baz"});
});
});
});
});