/** * 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 batchNode = require("nr-test-utils").require("@node-red/nodes/core/sequence/19-batch.js"); var helper = require("node-red-node-test-helper"); var RED = require("nr-test-utils").require("node-red/lib/red.js"); describe('BATCH node', function() { this.timeout(8000); before(function(done) { helper.startServer(done); }); after(function(done) { helper.stopServer(done); }); afterEach(function() { helper.unload(); RED.settings.nodeMessageBufferMaxLength = 0; }); it('should be loaded with defaults', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", wires:[["n2"]]}, {id:"n2", type:"helper"}]; helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); n1.should.have.property('name', 'BatchNode'); done(); }); }); function check_parts(msg, id, idx, count) { msg.should.have.property("parts"); var parts = msg.parts; parts.should.have.property("id", id); parts.should.have.property("index", idx); parts.should.have.property("count", count); } function check_data(n1, n2, results, done) { var id = undefined; var ix0 = 0; // seq no var ix1 = 0; // loc. in seq var seq = undefined; var msgs = []; n2.on("input", function(msg) { try { for (var i = 0; i < msgs.length; i++) { msg.should.not.equal(msgs[i]); } msgs.push(msg); if (seq === undefined) { seq = results[ix0]; } var val = seq[ix1]; msg.should.have.property("payload", val); if (id === undefined) { id = msg.parts.id; } check_parts(msg, id, ix1, seq.length); ix1++; if (ix1 === seq.length) { ix0++; ix1 = 0; seq = undefined; id = undefined; if (ix0 === results.length) { done(); } } } catch (e) { done(e); } }); } function check_count(flow, results, done) { try { helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); check_data(n1, n2, results, done); for(var i = 0; i < 6; i++) { n1.receive({payload: i}); } }); } catch (e) { done(e); } } function delayed_send(receiver, index, count, delay, done) { if (index < count) { setTimeout(function() { receiver.receive({payload: index}); delayed_send(receiver, index+1, count, delay, done); }, delay); } else if(index === count) { if (done) { done(); } } } function check_interval(flow, results, delay, done) { helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); check_data(n1, n2, results, done); delayed_send(n1, 0, 4, delay); }); } function check_concat(flow, results, inputs, done) { try { helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); check_data(n1, n2, results, done); for(var data of inputs) { var msg = { topic: data[0], payload: data[1], parts: { id: data[0], index: data[2], count: data[3] } }; n1.receive(msg); } }); } catch (e) { done(e); } } describe('mode: count', function() { it('should create seq. with count', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 2, overlap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ [0, 1], [2, 3], [4, 5] ]; check_count(flow, results, done); }); it('should create seq. with count and overlap', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 3, overlap: 2, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ [0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5] ]; check_count(flow, results, done); }); it('should handle too many pending messages', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 5, overlap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); RED.settings.nodeMessageBufferMaxLength = 2; setTimeout(function() { var logEvents = helper.log().args.filter(function (evt) { return evt[0].type == "batch"; }); var evt = logEvents[0][0]; evt.should.have.property('id', "n1"); evt.should.have.property('type', "batch"); evt.should.have.property('msg', "batch.too-many"); done(); }, 150); for(var i = 0; i < 3; i++) { n1.receive({payload: i}); } }); }); it('should handle reset', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 2, overlap: 0, interval: 0, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); var results = [ [0, 1], [4, 5] ]; check_data(n1, n2, results, done); n1.receive({payload:0}); n1.receive({payload:1}); n1.receive({payload:2}); n1.receive({payload:3, reset: true}); n1.receive({payload:4}); n1.receive({payload:5}); }); }); }); describe('mode: interval', function() { it('should create seq. with interval', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ [0, 1], [2, 3] ]; check_interval(flow, results, 450, done); }); it('should create seq. with interval (in float)', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 0.5, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ [0, 1], [2, 3] ]; check_interval(flow, results, 225, done); }); it('should create seq. with interval & not send empty seq', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ // 1300, 2600, 3900, 5200, [0], [1], [2], [3] ]; check_interval(flow, results, 1300, done); }); it('should create seq. with interval & send empty seq', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: true, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ // 1300, 2600, 3900, 5200, [null], [0], [1], [2], [null], [3] ]; check_interval(flow, results, 1300, done); }); it('should handle too many pending messages', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); RED.settings.nodeMessageBufferMaxLength = 2; setTimeout(function() { var logEvents = helper.log().args.filter(function (evt) { return evt[0].type == "batch"; }); var evt = logEvents[0][0]; evt.should.have.property('id', "n1"); evt.should.have.property('type', "batch"); evt.should.have.property('msg', "batch.too-many"); done(); }, 150); for(var i = 0; i < 3; i++) { n1.receive({payload: i}); } }); }); it('should handle reset', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); var results = [ [0, 1], [4, 5] ]; check_data(n1, n2, results, done); delayed_send(n1, 0, 3, 400, function () { setTimeout(function () { n1.receive({payload: "3", reset: true}); delayed_send(n1, 4, 7, 400); }, 10); }); }); }); }); describe('mode: concat', function() { it('should concat two seq. (series)', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ [2, 3, 0, 1] ]; var inputs = [ ["TB", 0, 0, 2], ["TB", 1, 1, 2], ["TA", 2, 0, 2], ["TA", 3, 1, 2] ]; check_concat(flow, results, inputs, done); }); it('should concat two seq. (mixed)', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ [2, 3, 0, 1] ]; var inputs = [ ["TA", 2, 0, 2], ["TB", 0, 0, 2], ["TA", 3, 1, 2], ["TB", 1, 1, 2] ]; check_concat(flow, results, inputs, done); }); it('should concat three seq.', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}, {topic: "TC"}], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ [2, 3, 0, 1, 4] ]; var inputs = [ ["TC", 4, 0, 1], ["TB", 0, 0, 2], ["TB", 1, 1, 2], ["TA", 2, 0, 2], ["TA", 3, 1, 2] ]; check_concat(flow, results, inputs, done); }); it('should concat same seq.', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TA"}], wires:[["n2"]]}, {id:"n2", type:"helper"}]; var results = [ [9, 8, 9, 8] ]; var inputs = [ ["TA", 9, 0, 2], ["TA", 8, 1, 2] ]; check_concat(flow, results, inputs, done); }); it('should handle too many pending messages', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]}, {id:"n2", type:"helper"}]; helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); RED.settings.nodeMessageBufferMaxLength = 2; setTimeout(function() { var logEvents = helper.log().args.filter(function (evt) { return evt[0].type == "batch"; }); var evt = logEvents[0][0]; evt.should.have.property('id', "n1"); evt.should.have.property('type', "batch"); evt.should.have.property('msg', "batch.too-many"); done(); }, 150); var C = 3; for(var i = 0; i < C; i++) { var parts_a = {index:i, count:C, id:"A"}; var parts_b = {index:i, count:C, id:"B"}; n1.receive({payload: i, topic: "TA", parts:parts_a}); n1.receive({payload: i, topic: "TB", parts:parts_b}); } }); }); it('should handle reset', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]}, {id:"n2", type:"helper"}]; try { helper.load(batchNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); var results = [ [2, 3, 0, 1] ]; check_data(n1, n2, results, done); var inputs0 = [ ["TB", 0, 0, 2], ["TA", 1, 0, 2], ]; for(var data of inputs0) { var msg = { topic: data[0], payload: data[1], parts: { id: data[0], index: data[2], count: data[3] } }; n1.receive(msg); } n1.receive({payload: undefined, reset: true}); var inputs1 = [ ["TB", 0, 0, 2], ["TB", 1, 1, 2], ["TA", 2, 0, 2], ["TA", 3, 1, 2] ]; for(var data of inputs1) { var msg = { topic: data[0], payload: data[1], parts: { id: data[0], index: data[2], count: data[3] } }; n1.receive(msg); } }); } catch (e) { done(e); } }); }); });