mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Add Flow spec
This commit is contained in:
parent
b0ffc12142
commit
c97ab18e62
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
* 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.
|
||||
@ -210,6 +210,7 @@ function Flow(config) {
|
||||
|
||||
this.activeNodes = {};
|
||||
this.subflowInstanceNodes = {};
|
||||
this.started = false;
|
||||
|
||||
this.parseConfig(config);
|
||||
|
||||
@ -251,7 +252,6 @@ Flow.prototype.parseConfig = function(config) {
|
||||
nodeType = nodeConfig.type;
|
||||
|
||||
if (nodeConfig.credentials) {
|
||||
credentials.extract(nodeConfig);
|
||||
delete nodeConfig.credentials;
|
||||
}
|
||||
|
||||
@ -297,6 +297,7 @@ Flow.prototype.parseConfig = function(config) {
|
||||
}
|
||||
|
||||
Flow.prototype.start = function() {
|
||||
this.started = true;
|
||||
if (this.missingTypes.length > 0) {
|
||||
throw new Error("missing types");
|
||||
}
|
||||
@ -354,6 +355,7 @@ Flow.prototype.stop = function(nodeList) {
|
||||
}
|
||||
when.settle(promises).then(function() {
|
||||
events.emit("nodes-stopped");
|
||||
flow.started = false;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@ -368,7 +370,7 @@ Flow.prototype.typeRegistered = function(type) {
|
||||
var i = this.missingTypes.indexOf(type);
|
||||
if (i != -1) {
|
||||
this.missingTypes.splice(i,1);
|
||||
if (this.missingTypes.length === 0) {
|
||||
if (this.missingTypes.length === 0 && this.started) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
@ -505,7 +507,7 @@ Flow.prototype.diffFlow = function(config) {
|
||||
|
||||
this.config.forEach(function(node) {
|
||||
for (var prop in node) {
|
||||
if (node.hasOwnProperty(prop) && prop != "z") {
|
||||
if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
|
||||
// This node has a property that references a changed node
|
||||
// Assume it is a config node change and mark this node as
|
||||
// changed.
|
||||
@ -553,7 +555,7 @@ Flow.prototype.diffFlow = function(config) {
|
||||
buildNodeLinks(newLinks,node,configNodes);
|
||||
});
|
||||
|
||||
var markLinkedNodes = function(linkChanged,changedNodes,linkMap,allNodes) {
|
||||
var markLinkedNodes = function(linkChanged,otherChangedNodes,linkMap,allNodes) {
|
||||
var stack = Object.keys(changedNodes);
|
||||
var visited = {};
|
||||
|
||||
@ -563,8 +565,8 @@ Flow.prototype.diffFlow = function(config) {
|
||||
var linkedNodes = linkMap[id];
|
||||
if (linkedNodes) {
|
||||
for (var i=0;i<linkedNodes.length;i++) {
|
||||
linkedNodeId = linkedNodes[i];
|
||||
if (changedNodes[linkedNodeId] || linkChanged[linkedNodeId]) {
|
||||
var linkedNodeId = linkedNodes[i];
|
||||
if (changedNodes[linkedNodeId] || deletedNodes[linkedNodeId] || otherChangedNodes[linkedNodeId] || linkChanged[linkedNodeId]) {
|
||||
// Do nothing - this linked node is already marked as changed, so will get done
|
||||
} else {
|
||||
linkChanged[linkedNodeId] = true;
|
||||
@ -575,9 +577,8 @@ Flow.prototype.diffFlow = function(config) {
|
||||
}
|
||||
}
|
||||
|
||||
markLinkedNodes(linkChangedNodes,changedNodes,newLinks,configNodes);
|
||||
markLinkedNodes(linkChangedNodes,deletedNodes,activeLinks,flow.allNodes);
|
||||
|
||||
markLinkedNodes(linkChangedNodes,{},newLinks,configNodes);
|
||||
markLinkedNodes(linkChangedNodes,{},activeLinks,flow.allNodes);
|
||||
|
||||
var modifiedLinkNodes = {};
|
||||
|
||||
@ -596,26 +597,28 @@ Flow.prototype.diffFlow = function(config) {
|
||||
newNodeLinks.forEach(function(link) {
|
||||
if (newLinkMap[link] != oldLinkMap[link]) {
|
||||
modifiedLinkNodes[node.id] = node;
|
||||
modifiedLinkNodes[link] = configNodes[link];
|
||||
linkChangedNodes[node.id] = node;
|
||||
if (!changedNodes[link] && !deletedNodes[link]) {
|
||||
modifiedLinkNodes[link] = configNodes[link];
|
||||
linkChangedNodes[link] = configNodes[link];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
oldNodeLinks.forEach(function(link) {
|
||||
if (newLinkMap[link] != oldLinkMap[link]) {
|
||||
modifiedLinkNodes[node.id] = node;
|
||||
modifiedLinkNodes[link] = configNodes[link];
|
||||
linkChangedNodes[node.id] = node;
|
||||
if (!changedNodes[link] && !deletedNodes[link]) {
|
||||
modifiedLinkNodes[link] = configNodes[link];
|
||||
linkChangedNodes[link] = configNodes[link];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
markLinkedNodes(linkChangedNodes,modifiedLinkNodes,newLinks,configNodes);
|
||||
|
||||
|
||||
//config.forEach(function(n) {
|
||||
// console.log((changedNodes[n.id]!=null)?"[C]":"[ ]",(linkChangedNodes[n.id]!=null)?"[L]":"[ ]","[ ]",n.id,n.type,n.name);
|
||||
//});
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
* Copyright 2014, 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.
|
||||
|
12
settings.js
12
settings.js
@ -20,6 +20,18 @@
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
adminAuth: {
|
||||
type: "credentials",
|
||||
users: [ {
|
||||
username: "nol",
|
||||
password: "5f4dcc3b5aa765d61d8327deb882cf99", // password
|
||||
permissions: "*"
|
||||
}],
|
||||
anonymous: {
|
||||
permissions: "read"
|
||||
}
|
||||
},
|
||||
// the tcp port that the Node-RED web server is listening on
|
||||
uiPort: 1880,
|
||||
|
||||
|
@ -0,0 +1,316 @@
|
||||
/**
|
||||
* 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 Flow = require("../../../red/nodes/Flow");
|
||||
|
||||
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 = [{id:"1",type:"test",foo:"b",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",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",["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 = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"c",wires:[[1]]},{id:"3",type:"test",foo:"a",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"]);
|
||||
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 = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[2]]},{id:"3",type:"test",foo:"a",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",[]);
|
||||
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 = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1],[1]]},{id:"3",type:"test",foo:"a",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",[]);
|
||||
diffResult.should.have.property("linked",["1","2"]);
|
||||
diffResult.should.have.property("wiringChanged",["2"]);
|
||||
});
|
||||
|
||||
it('identifies nodes with changed wiring - second connection removed', function() {
|
||||
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1],[1]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||
var newConfig = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",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",[]);
|
||||
diffResult.should.have.property("linked",["1","2"]);
|
||||
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 = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",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"]);
|
||||
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 = [{id:"1",type:"test",foo:"a",wires:[]},{id:"3",type:"test",foo:"a",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"]);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user