/** * Copyright 2015 IBM Corp. * * 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 clone = require('clone'); var util = require("util"); var Flow = require("../../../red/nodes/Flow"); var flows = require("../../../red/nodes/flows"); var Node = require("../../../red/nodes/Node"); var typeRegistry = require("../../../red/nodes/registry"); var credentials = require("../../../red/nodes/credentials"); describe('Flow', function() { describe('#constructor',function() { it('called with an empty flow',function() { var config = []; var flow = new Flow(config); config.should.eql(flow.getFlow()); var nodeCount = 0; flow.eachNode(function(node) { nodeCount++; }); nodeCount.should.equal(0); }); it('called with a non-empty flow with no missing types', function() { var getType = sinon.stub(typeRegistry,"get",function(type) { // For this test, don't care what the actual type is, just // that this returns a non-false result return {}; }); try { var config = [{id:"123",type:"test"}]; var flow = new Flow(config); config.should.eql(flow.getFlow()); flow.getMissingTypes().should.have.length(0); } finally { getType.restore(); } }); it('identifies missing types in a flow', function() { var getType = sinon.stub(typeRegistry,"get",function(type) { if (type == "test") { return {}; } else { return null; } }); try { var config = [{id:"123",type:"test"},{id:"456",type:"test1"},{id:"789",type:"test2"}]; var flow = new Flow(config); config.should.eql(flow.getFlow()); flow.getMissingTypes().should.eql(["test1","test2"]); } finally { getType.restore(); } }); it('extracts node credentials', function() { var getType = sinon.stub(typeRegistry,"get",function(type) { // For this test, don't care what the actual type is, just // that this returns a non-false result return {}; }); try { var config = [{id:"123",type:"test",credentials:{a:1,b:2}}]; var resultingConfig = clone(config); delete resultingConfig[0].credentials; var flow = new Flow(config); flow.getFlow().should.eql(resultingConfig); flow.getMissingTypes().should.have.length(0); } finally { getType.restore(); } }); }); describe('#start',function() { it('prevents a flow with missing types from starting', function() { var getType = sinon.stub(typeRegistry,"get",function(type) { if (type == "test") { return {}; } else { return null; } }); try { var config = [{id:"123",type:"test"},{id:"456",type:"test1"},{id:"789",type:"test2"}]; var flow = new Flow(config); flow.getMissingTypes().should.have.length(2); /*jshint immed: false */ (function() { flow.start(); }).should.throw(); } finally { getType.restore(); } }); }); describe('missing types',function() { it('removes missing types as they are registered', function() { var getType = sinon.stub(typeRegistry,"get",function(type) { if (type == "test") { return {}; } else { return null; } }); var flowStart; try { var config = [{id:"123",type:"test"},{id:"456",type:"test1"},{id:"789",type:"test2"}]; var flow = new Flow(config); flowStart = sinon.stub(flow,"start",function() {this.started = true;}); config.should.eql(flow.getFlow()); flow.getMissingTypes().should.eql(["test1","test2"]); flow.typeRegistered("test1"); flow.getMissingTypes().should.eql(["test2"]); flowStart.called.should.be.false; flow.typeRegistered("test2"); flow.getMissingTypes().should.eql([]); flowStart.called.should.be.false; } finally { flowStart.restore(); getType.restore(); } }); it('starts flows once all missing types are registered', function() { var getType = sinon.stub(typeRegistry,"get",function(type) { if (type == "test") { return {}; } else { return null; } }); var flowStart; try { var config = [{id:"123",type:"test"},{id:"456",type:"test1"},{id:"789",type:"test2"}]; var flow = new Flow(config); // First call to .start throws err due to missing types /*jshint immed: false */ (function() { flow.start(); }).should.throw(); // Stub .start so when missing types are registered, we don't actually try starting them flowStart = sinon.stub(flow,"start",function() {}); config.should.eql(flow.getFlow()); flow.getMissingTypes().should.eql(["test1","test2"]); flow.typeRegistered("test1"); flow.typeRegistered("test2"); flow.getMissingTypes().should.have.length(0); flowStart.called.should.be.true; } finally { flowStart.restore(); getType.restore(); } }); }); describe('#diffFlow',function() { var getType; before(function() { getType = sinon.stub(typeRegistry,"get",function(type) { // For this test, don't care what the actual type is, just // that this returns a non-false result return {}; }); }); after(function() { getType.restore(); }); it('handles an identical configuration', function() { var config = [{id:"123",type:"test",foo:"a",wires:[]}]; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(config); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",[]); diffResult.should.have.property("linked",[]); diffResult.should.have.property("wiringChanged",[]); }); it('identifies nodes with changed properties, including downstream linked', function() { var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = clone(config); newConfig[0].foo = "b"; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["1"]); diffResult.should.have.property("linked",["2"]); diffResult.should.have.property("wiringChanged",[]); }); it('identifies nodes with changed properties, including upstream linked', function() { var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = clone(config); newConfig[1].bar = "c"; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["2"]); diffResult.should.have.property("linked",["1"]); diffResult.should.have.property("wiringChanged",[]); }); it('identifies nodes with changed credentials, including downstream linked', function() { var config = [{id:"1",type:"test",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = clone(config); newConfig[0].credentials = {}; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["1"]); diffResult.should.have.property("linked",["2"]); diffResult.should.have.property("wiringChanged",[]); }); it('identifies nodes with changed wiring', function() { var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = clone(config); newConfig[1].wires[0][0] = 2; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",[]); diffResult.should.have.property("linked",["1","2"]); diffResult.should.have.property("wiringChanged",["2"]); }); it('identifies nodes with changed wiring - second connection added', function() { var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = clone(config); newConfig[1].wires[0].push([1]); var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",[]); diffResult.should.have.property("linked",["1","2"]); diffResult.should.have.property("wiringChanged",["2"]); }); it('identifies nodes with changed wiring - second connection added', function() { var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = clone(config); newConfig[1].wires[0].push([1]); var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",[]); diffResult.should.have.property("linked",["1","2"]); diffResult.should.have.property("wiringChanged",["2"]); }); it('identifies nodes with changed wiring - node connected', function() { var config = [{id:"1",type:"test",foo:"a",wires:[["2"]]},{id:"2",type:"test",bar:"b",wires:[[]]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = clone(config); newConfig[1].wires.push("3"); var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",[]); diffResult.should.have.property("linked",["1","2","3"]); diffResult.should.have.property("wiringChanged",["2"]); }); it('identifies new nodes', function() { var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = clone(config); newConfig.push({id:"2",type:"test",bar:"b",wires:[[1]]}); var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["2"]); diffResult.should.have.property("linked",["1"]); diffResult.should.have.property("wiringChanged",[]); }); it('identifies deleted nodes', function() { var config = [{id:"1",type:"test",foo:"a",wires:[[2]]},{id:"2",type:"test",bar:"b",wires:[[3]]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = clone(config); newConfig.splice(1,1); newConfig[0].wires = []; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",["2"]); diffResult.should.have.property("changed",[]); diffResult.should.have.property("linked",["1","3"]); diffResult.should.have.property("wiringChanged",["1"]); }); it('identifies config nodes changes', function() { var config = [{id:"1",type:"test",foo:"configNode",wires:[[2]]},{id:"2",type:"test",bar:"b",wires:[[3]]},{id:"3",type:"test",foo:"a",wires:[]},{id:"configNode",type:"testConfig"}]; var newConfig = clone(config); newConfig[3].foo = "bar"; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["1","configNode"]); diffResult.should.have.property("linked",["2","3"]); diffResult.should.have.property("wiringChanged",[]); }); it('marks a parent subflow as changed for an internal property change', function() { var config = [ {id:"1",type:"test",wires:[[2]]}, {id:"2",type:"subflow:sf1",wires:[[3]]}, {id:"3",type:"test",wires:[]}, {id:"sf1",type:"subflow"}, {id:"sf1-1",z:"sf1",type:"test",foo:"a"}, {id:"4",type:"subflow:sf1"} ]; var newConfig = clone(config); newConfig[4].foo = "b"; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["2","4"]); diffResult.should.have.property("linked",["1","3"]); diffResult.should.have.property("wiringChanged",[]); }); it('marks a parent subflow as changed for an internal wiring change', function() { var config = [ {id:"1",type:"test",wires:[[2]]}, {id:"2",type:"subflow:sf1",wires:[[3]]}, {id:"3",type:"test",wires:[]}, {id:"sf1",type:"subflow"}, {id:"sf1-1",z:"sf1",type:"test"}, {id:"sf1-2",z:"sf1",type:"test"} ]; var newConfig = clone(config); newConfig[4].wires = [["sf1-2"]]; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["2"]); diffResult.should.have.property("linked",["1","3"]); diffResult.should.have.property("wiringChanged",[]); }); it('marks a parent subflow as changed for an internal node delete', function() { var config = [ {id:"1",type:"test",wires:[[2]]}, {id:"2",type:"subflow:sf1",wires:[[3]]}, {id:"3",type:"test",wires:[]}, {id:"sf1",type:"subflow"}, {id:"sf1-1",z:"sf1",type:"test"}, {id:"sf1-2",z:"sf1",type:"test"} ]; var newConfig = clone(config); newConfig.splice(5,1); var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["2"]); diffResult.should.have.property("linked",["1","3"]); diffResult.should.have.property("wiringChanged",[]); }); it('marks a parent subflow as changed for an internal subflow wiring change - input removed', function() { var config = [ {id:"1",type:"test",wires:[[2]]}, {id:"2",type:"subflow:sf1",wires:[[3]]}, {id:"3",type:"test",wires:[]}, {id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]}, {id:"sf1-1",z:"sf1",type:"test"}, {id:"sf1-2",z:"sf1",type:"test"} ]; var newConfig = clone(config); newConfig[3].in[0].wires = []; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["2"]); diffResult.should.have.property("linked",["1","3"]); diffResult.should.have.property("wiringChanged",[]); }); it('marks a parent subflow as changed for an internal subflow wiring change - input added', function() { var config = [ {id:"1",type:"test",wires:[[2]]}, {id:"2",type:"subflow:sf1",wires:[[3]]}, {id:"3",type:"test",wires:[]}, {id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]}, {id:"sf1-1",z:"sf1",type:"test"}, {id:"sf1-2",z:"sf1",type:"test"} ]; var newConfig = clone(config); newConfig[3].in[0].wires.push({"id":"sf1-2"}); var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["2"]); diffResult.should.have.property("linked",["1","3"]); diffResult.should.have.property("wiringChanged",[]); }); it('marks a parent subflow as changed for an internal subflow wiring change - output added', function() { var config = [ {id:"1",type:"test",wires:[[2]]}, {id:"2",type:"subflow:sf1",wires:[[3]]}, {id:"3",type:"test",wires:[]}, {id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]}, {id:"sf1-1",z:"sf1",type:"test"}, {id:"sf1-2",z:"sf1",type:"test"} ]; var newConfig = clone(config); newConfig[3].out[0].wires.push({"id":"sf1-2","port":0}); var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["2"]); diffResult.should.have.property("linked",["1","3"]); diffResult.should.have.property("wiringChanged",[]); }); it('marks a parent subflow as changed for an internal subflow wiring change - output removed', function() { var config = [ {id:"1",type:"test",wires:[[2]]}, {id:"2",type:"subflow:sf1",wires:[[3]]}, {id:"3",type:"test",wires:[]}, {id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]}, {id:"sf1-1",z:"sf1",type:"test"}, {id:"sf1-2",z:"sf1",type:"test"} ]; var newConfig = clone(config); newConfig[3].out[0].wires = []; var flow = new Flow(config); flow.getMissingTypes().should.have.length(0); var diffResult = flow.diffFlow(newConfig); diffResult.should.have.property("deleted",[]); diffResult.should.have.property("changed",["2"]); diffResult.should.have.property("linked",["1","3"]); diffResult.should.have.property("wiringChanged",[]); }); }); describe('#applyConfig',function() { var getType; var getNode; var credentialsClean; var stoppedNodes = {}; var currentNodes = {}; var TestNode = function(n) { Node.call(this,n); var node = this; this.handled = 0; this.stopped = false; currentNodes[node.id] = node; this.on('input',function(msg) { node.handled++; node.send(msg); }); this.on('close',function() { node.stopped = true; stoppedNodes[node.id] = node; delete currentNodes[node.id]; }); } util.inherits(TestNode,Node); before(function() { getNode = sinon.stub(flows,"get",function(id) { return currentNodes[id]; }); getType = sinon.stub(typeRegistry,"get",function(type) { return TestNode; }); credentialsClean = sinon.stub(credentials,"clean",function(config){}); }); after(function() { getType.restore(); credentialsClean.restore(); getNode.restore(); }); beforeEach(function() { currentNodes = {}; stoppedNodes = {}; }); it("instantiates an initial configuration and stops it",function(done) { var config = [{id:"1",type:"test",foo:"a",wires:["2"]},{id:"2",type:"test",bar:"b",wires:[["3"]]},{id:"3",type:"test",foo:"a",wires:[]}]; var flow = new Flow(config); flow.start(); currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); currentNodes["1"].should.have.a.property("handled",0); currentNodes["2"].should.have.a.property("handled",0); currentNodes["3"].should.have.a.property("handled",0); currentNodes["1"].receive({payload:"test"}); currentNodes["1"].should.have.a.property("handled",1); currentNodes["2"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",1); flow.stop().then(function() { currentNodes.should.not.have.a.property("1"); currentNodes.should.not.have.a.property("2"); currentNodes.should.not.have.a.property("3"); stoppedNodes.should.have.a.property("1"); stoppedNodes.should.have.a.property("2"); stoppedNodes.should.have.a.property("3"); done(); }); }); it("stops all nodes on new full deploy",function(done) { var config = [{id:"1",type:"test",foo:"a",wires:["2"]},{id:"2",type:"test",bar:"b",wires:[["3"]]},{id:"3",type:"test",foo:"a",wires:[]}]; var newConfig = [{id:"4",type:"test",foo:"a",wires:["5"]},{id:"5",type:"test",bar:"b",wires:[["6"]]},{id:"6",type:"test",foo:"a",wires:[]}]; var flow = new Flow(config); flow.start(); currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); flow.applyConfig(newConfig).then(function() { currentNodes.should.not.have.a.property("1"); currentNodes.should.not.have.a.property("2"); currentNodes.should.not.have.a.property("3"); stoppedNodes.should.have.a.property("1"); stoppedNodes.should.have.a.property("2"); stoppedNodes.should.have.a.property("3"); currentNodes.should.have.a.property("4"); currentNodes.should.have.a.property("5"); currentNodes.should.have.a.property("6"); done(); }); }); it("stops only modified nodes on 'nodes' deploy",function(done) { var config = [{id:"1",type:"test",name:"a",wires:["2"]},{id:"2",type:"test",name:"b",wires:[["3"]]},{id:"3",type:"test",name:"c",wires:[]}]; var newConfig = clone(config); newConfig[1].name = "B"; var flow = new Flow(config); flow.start(); currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); currentNodes["2"].should.have.a.property("name","b"); currentNodes["1"].receive({payload:"test"}); currentNodes["1"].should.have.a.property("handled",1); currentNodes["2"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",1); flow.applyConfig(newConfig,"nodes").then(function() { currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); currentNodes["2"].should.have.a.property("name","B"); stoppedNodes.should.not.have.a.property("1"); stoppedNodes.should.have.a.property("2"); stoppedNodes.should.not.have.a.property("3"); stoppedNodes["2"].should.have.a.property("name","b"); currentNodes["1"].receive({payload:"test"}); currentNodes["1"].should.have.a.property("handled",2); currentNodes["2"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",2); done(); }); }); it("stops only modified flows on 'flows' deploy",function(done) { var config = [{id:"1",type:"test",name:"a",wires:["2"]},{id:"2",type:"test",name:"b",wires:[[]]},{id:"3",type:"test",name:"c",wires:[]}]; var newConfig = clone(config); newConfig[1].name = "B"; var flow = new Flow(config); flow.start(); currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); currentNodes["2"].should.have.a.property("name","b"); currentNodes["1"].receive({payload:"test"}); currentNodes["1"].should.have.a.property("handled",1); currentNodes["2"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",0); currentNodes["3"].receive({payload:"test"}); currentNodes["3"].should.have.a.property("handled",1); flow.applyConfig(newConfig,"flows").then(function() { currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); currentNodes["2"].should.have.a.property("name","B"); stoppedNodes.should.have.a.property("1"); stoppedNodes.should.have.a.property("2"); stoppedNodes.should.not.have.a.property("3"); stoppedNodes["2"].should.have.a.property("name","b"); currentNodes["1"].receive({payload:"test"}); currentNodes["1"].should.have.a.property("handled",1); currentNodes["2"].should.have.a.property("handled",1); currentNodes["3"].receive({payload:"test"}); currentNodes["3"].should.have.a.property("handled",2); done(); }); }); it("rewires otherwise unmodified nodes on 'nodes' deploy",function(done) { var config = [{id:"1",type:"test",name:"a",wires:["2"]},{id:"2",type:"test",name:"b",wires:[[]]},{id:"3",type:"test",name:"c",wires:[]}]; var newConfig = clone(config); newConfig[1].wires[0].push("3"); var flow = new Flow(config); flow.start(); currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); currentNodes["1"].receive({payload:"test"}); currentNodes["1"].should.have.a.property("handled",1); currentNodes["2"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",0); flow.applyConfig(newConfig,"nodes").then(function() { currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); stoppedNodes.should.not.have.a.property("1"); stoppedNodes.should.not.have.a.property("2"); stoppedNodes.should.not.have.a.property("3"); currentNodes["1"].receive({payload:"test"}); currentNodes["1"].should.have.a.property("handled",2); currentNodes["2"].should.have.a.property("handled",2); currentNodes["3"].should.have.a.property("handled",1); done(); }); }); it("stops rewired but otherwise unmodified nodes on 'flows' deploy",function(done) { var config = [{id:"1",type:"test",name:"a",wires:["2"]},{id:"2",type:"test",name:"b",wires:[[]]},{id:"3",type:"test",name:"c",wires:[]}]; var newConfig = clone(config); newConfig[1].wires[0].push("3"); var flow = new Flow(config); flow.start(); currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); currentNodes["1"].receive({payload:"test"}); currentNodes["1"].should.have.a.property("handled",1); currentNodes["2"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",0); flow.applyConfig(newConfig,"flows").then(function() { currentNodes.should.have.a.property("1"); currentNodes.should.have.a.property("2"); currentNodes.should.have.a.property("3"); stoppedNodes.should.have.a.property("1"); stoppedNodes.should.have.a.property("2"); stoppedNodes.should.have.a.property("3"); currentNodes["1"].receive({payload:"test"}); currentNodes["1"].should.have.a.property("handled",1); currentNodes["2"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",1); done(); }); }); }); describe('#handleError',function() { var getType; var getNode; var credentialsClean; var currentNodes = {}; var TestNode = function(n) { Node.call(this,n); var node = this; this.handled = []; currentNodes[node.id] = node; this.on('input',function(msg) { node.handled.push(msg); node.send(msg); }); } util.inherits(TestNode,Node); before(function() { getNode = sinon.stub(flows,"get",function(id) { return currentNodes[id]; }); getType = sinon.stub(typeRegistry,"get",function(type) { return TestNode; }); credentialsClean = sinon.stub(credentials,"clean",function(config){}); }); after(function() { getType.restore(); credentialsClean.restore(); getNode.restore(); }); beforeEach(function() { currentNodes = {}; }); it("reports error to catch nodes on same z",function(done) { var config = [ {id:"1",type:"test",z:"tab1",name:"a",wires:["2"]}, {id:"2",type:"catch",z:"tab1",wires:[[]]}, {id:"3",type:"catch",z:"tab2",wires:[[]]} ]; var flow = new Flow(config); flow.start(); var msg = {a:1}; flow.handleError(getNode(1),"test error",msg); var n2 = getNode(2); n2.handled.should.have.lengthOf(1); n2.handled[0].should.have.property("a",1); n2.handled[0].should.have.property("error"); n2.handled[0].error.should.have.property("message","test error"); n2.handled[0].error.should.have.property("source"); n2.handled[0].error.source.should.have.property("id","1"); n2.handled[0].error.source.should.have.property("type","test"); getNode(3).handled.should.have.lengthOf(0); done(); }); it("reports error with Error object",function(done) { var config = [ {id:"1",type:"test",z:"tab1",name:"a",wires:["2"]}, {id:"2",type:"catch",z:"tab1",wires:[[]]}, {id:"3",type:"catch",z:"tab2",wires:[[]]} ]; var flow = new Flow(config); flow.start(); var msg = {a:1,error:"existing"}; flow.handleError(getNode(1),"test error",msg); var n2 = getNode(2); n2.handled.should.have.lengthOf(1); n2.handled[0].should.have.property("error"); n2.handled[0].should.have.property("_error","existing"); n2.handled[0].error.should.have.property("message","test error"); n2.handled[0].error.should.have.property("source"); n2.handled[0].error.source.should.have.property("id","1"); n2.handled[0].error.source.should.have.property("type","test"); getNode(3).handled.should.have.lengthOf(0); done(); }); it("reports error with Error object",function(done) { var config = [ {id:"1",type:"test",z:"tab1",name:"a",wires:["2"]}, {id:"2",type:"catch",z:"tab1",wires:[[]]}, {id:"3",type:"catch",z:"tab2",wires:[[]]} ]; var flow = new Flow(config); flow.start(); flow.handleError(getNode(1),new Error("test error")); var n2 = getNode(2); n2.handled.should.have.lengthOf(1); n2.handled[0].should.have.property("error"); n2.handled[0].error.should.have.property("message","Error: test error"); n2.handled[0].error.should.have.property("source"); n2.handled[0].error.source.should.have.property("id","1"); n2.handled[0].error.source.should.have.property("type","test"); getNode(3).handled.should.have.lengthOf(0); done(); }); it('reports error in subflow to a local handler', function(done) { var config = [ {id:"1",type:"test",z:"tab1",wires:[[]]}, {id:"2",type:"subflow:sf1",z:"tab1",wires:[[]]}, {id:"3",type:"catch",z:"tab1",wires:[]}, {id:"sf1",type:"subflow","in": [],"out": []}, {id:"sf1-1",z:"sf1",type:"test",wires:[]}, {id:"sf1-catch",type:"catch",z:"sf1",wires:[]} ]; var flow = new Flow(config); flow.start(); var instanceNode; var instanceCatch; for (var id in currentNodes) { if (currentNodes.hasOwnProperty(id)) { if (currentNodes[id].z == '2') { if (currentNodes[id].type == "catch") { instanceCatch = currentNodes[id]; } else { instanceNode = currentNodes[id]; } } } } flow.handleError(instanceNode,new Error("test error")); var n3 = instanceCatch; n3.handled.should.have.lengthOf(1); n3.handled[0].should.have.property("error"); n3.handled[0].error.should.have.property("message","Error: test error"); n3.handled[0].error.should.have.property("source"); n3.handled[0].error.source.should.have.property("id",instanceNode.id); n3.handled[0].error.source.should.have.property("type","test"); getNode(3).handled.should.have.lengthOf(0); done(); }); it('reports error in subflow to a parent handler', function(done) { var config = [ {id:"1",type:"test",z:"tab1",wires:[[]]}, {id:"2",type:"subflow:sf1",z:"tab1",wires:[[]]}, {id:"3",type:"catch",z:"tab1",wires:[]}, {id:"4",type:"catch",z:"tab2",wires:[]}, {id:"sf1",type:"subflow","in": [],"out": []}, {id:"sf1-1",z:"sf1",type:"test",wires:[]} ]; var flow = new Flow(config); flow.start(); var instanceNode; for (var id in currentNodes) { if (currentNodes.hasOwnProperty(id)) { if (currentNodes[id].z == '2') { instanceNode = currentNodes[id]; } } } flow.handleError(instanceNode,new Error("test error")); var n3 = getNode(3); n3.handled.should.have.lengthOf(1); n3.handled[0].should.have.property("error"); n3.handled[0].error.should.have.property("message","Error: test error"); n3.handled[0].error.should.have.property("source"); n3.handled[0].error.source.should.have.property("id",instanceNode.id); n3.handled[0].error.source.should.have.property("type","test"); getNode(4).handled.should.have.lengthOf(0); done(); }); }); });