1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge pull request #2227 from node-red/node-done

Adds Done callback to Input event handler
This commit is contained in:
Nick O'Leary 2019-08-21 11:15:00 +01:00 committed by GitHub
commit b734097d16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1333 additions and 565 deletions

View File

@ -0,0 +1,136 @@
<script type="text/x-red" data-template-name="complete">
<div class="form-row node-input-target-row">
<button id="node-input-complete-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
</div>
<div class="form-row node-input-target-row node-input-target-list-row" style="min-height: 100px">
<div id="node-input-complete-target-container-div"></div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('complete',{
category: 'input',
color:"#c0edc0",
defaults: {
name: {value:""},
scope: {value:[]},
uncaught: {value:false}
},
inputs:0,
outputs:1,
icon: "alert.svg",
label: function() {
if (this.name) {
return this.name;
}
return this._("complete.completeNodes",{number:this.scope.length});
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var node = this;
var scope = node.scope || [];
this._resize = function() {
var rows = $("#dialog-form>div:not(.node-input-target-list-row)");
var height = $("#dialog-form").height();
for (var i=0;i<rows.length;i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-target-list-row");
editorRow.css("height",height+"px");
};
var dirList = $("#node-input-complete-target-container-div").css({width: "100%", height: "100%"})
.treeList({multi:true}).on("treelistitemmouseover", function(e, item) {
item.node.highlighted = true;
item.node.dirty = true;
RED.view.redraw();
}).on("treelistitemmouseout", function(e, item) {
item.node.highlighted = false;
item.node.dirty = true;
RED.view.redraw();
})
var candidateNodes = RED.nodes.filterNodes({z:node.z});
var allChecked = true;
var items = [];
var nodeItemMap = {};
candidateNodes.forEach(function(n) {
if (n.id === node.id) {
return;
}
var isChecked = scope.indexOf(n.id) !== -1;
allChecked = allChecked && isChecked;
var nodeDef = RED.nodes.getType(n.type);
var label;
var sublabel;
if (nodeDef) {
var l = nodeDef.label;
label = (typeof l === "function" ? l.call(n) : l)||"";
sublabel = n.type;
if (sublabel.indexOf("subflow:") === 0) {
var subflowId = sublabel.substring(8);
var subflow = RED.nodes.subflow(subflowId);
sublabel = "subflow : "+subflow.name;
}
}
if (!nodeDef || !label) {
label = n.type;
}
nodeItemMap[n.id] = {
node: n,
label: label,
sublabel: sublabel,
selected: isChecked
};
items.push(nodeItemMap[n.id]);
});
dirList.treeList('data',items);
$("#node-input-complete-target-select").on("click", function(e) {
e.preventDefault();
var preselected = dirList.treeList('selected').map(function(n) {return n.node.id});
RED.tray.hide();
RED.view.selectNodes({
selected: preselected,
onselect: function(selection) {
RED.tray.show();
var newlySelected = {};
selection.forEach(function(n) {
newlySelected[n.id] = true;
if (nodeItemMap[n.id]) {
nodeItemMap[n.id].treeList.select(true);
}
})
preselected.forEach(function(id) {
if (!newlySelected[id]) {
nodeItemMap[id].treeList.select(false);
}
})
},
oncancel: function() {
RED.tray.show();
},
filter: function(n) {
return n.id !== node.id;
}
});
})
},
oneditsave: function() {
this.scope = $("#node-input-complete-target-container-div").treeList('selected').map(function(i) { return i.node.id})
},
oneditresize: function(size) {
this._resize();
}
});
</script>

View File

@ -0,0 +1,30 @@
/**
* 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.
**/
module.exports = function(RED) {
"use strict";
function CompleteNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.scope = n.scope;
this.on("input",function(msg) {
this.send(msg);
});
}
RED.nodes.registerType("complete",CompleteNode);
}

View File

@ -81,7 +81,7 @@ module.exports = function(RED) {
} }
} }
this.on("input", function(msg) { this.on("input", function(msg, send, done) {
if (this.complete === "true") { if (this.complete === "true") {
// debug complete msg object // debug complete msg object
if (this.console === "true") { if (this.console === "true") {
@ -90,13 +90,14 @@ module.exports = function(RED) {
if (this.active && this.tosidebar) { if (this.active && this.tosidebar) {
sendDebug({id:node.id, name:node.name, topic:msg.topic, msg:msg, _path:msg._path}); sendDebug({id:node.id, name:node.name, topic:msg.topic, msg:msg, _path:msg._path});
} }
done();
} else { } else {
prepareValue(msg,function(err,msg) { prepareValue(msg,function(err,debugMsg) {
if (err) { if (err) {
node.error(err); node.error(err);
return; return;
} }
var output = msg.msg; var output = debugMsg.msg;
if (node.console === "true") { if (node.console === "true") {
if (typeof output === "string") { if (typeof output === "string") {
node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output); node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output);
@ -114,9 +115,10 @@ module.exports = function(RED) {
} }
if (node.active) { if (node.active) {
if (node.tosidebar == true) { if (node.tosidebar == true) {
sendDebug(msg); sendDebug(debugMsg);
} }
} }
done();
}); });
} }
}) })

View File

@ -38,7 +38,7 @@ module.exports = function(RED) {
//node.error("Exec node timeout"); //node.error("Exec node timeout");
} }
this.on("input", function(msg) { this.on("input", function(msg, nodeSend, nodeDone) {
if (msg.hasOwnProperty("kill")) { if (msg.hasOwnProperty("kill")) {
if (typeof msg.kill !== "string" || msg.kill.length === 0 || !msg.kill.toUpperCase().startsWith("SIG") ) { msg.kill = "SIGTERM"; } if (typeof msg.kill !== "string" || msg.kill.length === 0 || !msg.kill.toUpperCase().startsWith("SIG") ) { msg.kill = "SIGTERM"; }
if (msg.hasOwnProperty("pid")) { if (msg.hasOwnProperty("pid")) {
@ -53,6 +53,7 @@ module.exports = function(RED) {
node.status({fill:"red",shape:"dot",text:"killed"}); node.status({fill:"red",shape:"dot",text:"killed"});
} }
} }
nodeDone();
} }
else { else {
var child; var child;
@ -85,14 +86,14 @@ module.exports = function(RED) {
// console.log('[exec] stdout: ' + data,child.pid); // console.log('[exec] stdout: ' + data,child.pid);
if (isUtf8(data)) { msg.payload = data.toString(); } if (isUtf8(data)) { msg.payload = data.toString(); }
else { msg.payload = data; } else { msg.payload = data; }
node.send([RED.util.cloneMessage(msg),null,null]); nodeSend([RED.util.cloneMessage(msg),null,null]);
} }
}); });
child.stderr.on('data', function (data) { child.stderr.on('data', function (data) {
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) { if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
if (isUtf8(data)) { msg.payload = data.toString(); } if (isUtf8(data)) { msg.payload = data.toString(); }
else { msg.payload = Buffer.from(data); } else { msg.payload = Buffer.from(data); }
node.send([null,RED.util.cloneMessage(msg),null]); nodeSend([null,RED.util.cloneMessage(msg),null]);
} }
}); });
child.on('close', function (code,signal) { child.on('close', function (code,signal) {
@ -108,8 +109,9 @@ module.exports = function(RED) {
if (code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); } if (code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
else if (code < 0) { node.status({fill:"red",shape:"dot",text:"rc:"+code}); } else if (code < 0) { node.status({fill:"red",shape:"dot",text:"rc:"+code}); }
else { node.status({fill:"yellow",shape:"dot",text:"rc:"+code}); } else { node.status({fill:"yellow",shape:"dot",text:"rc:"+code}); }
node.send([null,null,RED.util.cloneMessage(msg)]); nodeSend([null,null,RED.util.cloneMessage(msg)]);
} }
nodeDone();
}); });
child.on('error', function (code) { child.on('error', function (code) {
if (child.tout) { clearTimeout(child.tout); } if (child.tout) { clearTimeout(child.tout); }
@ -154,9 +156,10 @@ module.exports = function(RED) {
msg.rc = msg3.payload; msg.rc = msg3.payload;
if (msg2) { msg2.rc = msg3.payload; } if (msg2) { msg2.rc = msg3.payload; }
} }
node.send([msg,msg2,msg3]); nodeSend([msg,msg2,msg3]);
if (child.tout) { clearTimeout(child.tout); } if (child.tout) { clearTimeout(child.tout); }
delete node.activeProcesses[child.pid]; delete node.activeProcesses[child.pid];
nodeDone();
}); });
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid}); node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
child.on('error',function() {}); child.on('error',function() {});

View File

@ -19,7 +19,7 @@ module.exports = function(RED) {
var util = require("util"); var util = require("util");
var vm = require("vm"); var vm = require("vm");
function sendResults(node,_msgid,msgs) { function sendResults(node,send,_msgid,msgs) {
if (msgs == null) { if (msgs == null) {
return; return;
} else if (!util.isArray(msgs)) { } else if (!util.isArray(msgs)) {
@ -49,7 +49,7 @@ module.exports = function(RED) {
} }
} }
if (msgCount>0) { if (msgCount>0) {
node.send(msgs); send(msgs);
} }
} }
@ -58,8 +58,17 @@ module.exports = function(RED) {
var node = this; var node = this;
this.name = n.name; this.name = n.name;
this.func = n.func; this.func = n.func;
var handleNodeDoneCall = true;
// Check to see if the Function appears to call `node.done()`. If so,
// we will assume it is well written and does actually call node.done().
// Otherwise, we will call node.done() after the function returns regardless.
if (/node\.done\s*\(\s*\)/.test(this.func)) {
handleNodeDoneCall = false;
}
var functionText = "var results = null;"+ var functionText = "var results = null;"+
"results = (function(msg){ "+ "results = (function(msg,__send__,__done__){ "+
"var __msgid__ = msg._msgid;"+ "var __msgid__ = msg._msgid;"+
"var node = {"+ "var node = {"+
"id:__node__.id,"+ "id:__node__.id,"+
@ -71,10 +80,11 @@ module.exports = function(RED) {
"trace:__node__.trace,"+ "trace:__node__.trace,"+
"on:__node__.on,"+ "on:__node__.on,"+
"status:__node__.status,"+ "status:__node__.status,"+
"send:function(msgs){ __node__.send(__msgid__,msgs);}"+ "send:function(msgs){ __node__.send(__send__,__msgid__,msgs);},"+
"done:__done__"+
"};\n"+ "};\n"+
this.func+"\n"+ this.func+"\n"+
"})(msg);"; "})(msg,send,done);";
this.topic = n.topic; this.topic = n.topic;
this.outstandingTimers = []; this.outstandingTimers = [];
this.outstandingIntervals = []; this.outstandingIntervals = [];
@ -104,8 +114,8 @@ module.exports = function(RED) {
trace: function() { trace: function() {
node.trace.apply(node, arguments); node.trace.apply(node, arguments);
}, },
send: function(id, msgs) { send: function(send, id, msgs) {
sendResults(node, id, msgs); sendResults(node, send, id, msgs);
}, },
on: function() { on: function() {
if (arguments[0] === "input") { if (arguments[0] === "input") {
@ -223,12 +233,18 @@ module.exports = function(RED) {
// lineOffset: -11, // line number offset to be used for stack traces // lineOffset: -11, // line number offset to be used for stack traces
// columnOffset: 0, // column number offset to be used for stack traces // columnOffset: 0, // column number offset to be used for stack traces
}); });
this.on("input", function(msg) { this.on("input", function(msg,send,done) {
try { try {
var start = process.hrtime(); var start = process.hrtime();
context.msg = msg; context.msg = msg;
context.send = send;
context.done = done;
this.script.runInContext(context); this.script.runInContext(context);
sendResults(this,msg._msgid,context.results); sendResults(this,send,msg._msgid,context.results);
if (handleNodeDoneCall) {
done();
}
var duration = process.hrtime(start); var duration = process.hrtime(start);
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100; var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;

View File

@ -334,7 +334,7 @@ module.exports = function(RED) {
} }
}; };
this.publish = function (msg) { this.publish = function (msg,done) {
if (node.connected) { if (node.connected) {
if (msg.payload === null || msg.payload === undefined) { if (msg.payload === null || msg.payload === undefined) {
msg.payload = ""; msg.payload = "";
@ -350,7 +350,10 @@ module.exports = function(RED) {
qos: msg.qos || 0, qos: msg.qos || 0,
retain: msg.retain || false retain: msg.retain || false
}; };
node.client.publish(msg.topic, msg.payload, options, function(err) {return}); node.client.publish(msg.topic, msg.payload, options, function(err) {
done && done();
return
});
} }
}; };
@ -453,7 +456,7 @@ module.exports = function(RED) {
if (this.brokerConn) { if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
this.on("input",function(msg) { this.on("input",function(msg,send,done) {
if (msg.qos) { if (msg.qos) {
msg.qos = parseInt(msg.qos); msg.qos = parseInt(msg.qos);
if ((msg.qos !== 0) && (msg.qos !== 1) && (msg.qos !== 2)) { if ((msg.qos !== 0) && (msg.qos !== 1) && (msg.qos !== 2)) {
@ -468,9 +471,13 @@ module.exports = function(RED) {
} }
if ( msg.hasOwnProperty("payload")) { if ( msg.hasOwnProperty("payload")) {
if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
this.brokerConn.publish(msg); // send the message this.brokerConn.publish(msg, done); // send the message
} else {
node.warn(RED._("mqtt.errors.invalid-topic"));
done();
} }
else { node.warn(RED._("mqtt.errors.invalid-topic")); } } else {
done();
} }
}); });
if (this.brokerConn.connected) { if (this.brokerConn.connected) {

View File

@ -50,7 +50,7 @@ module.exports = function(RED) {
noprox = proxyConfig.noproxy; noprox = proxyConfig.noproxy;
} }
this.on("input",function(msg) { this.on("input",function(msg,nodeSend,nodeDone) {
var preRequestTimestamp = process.hrtime(); var preRequestTimestamp = process.hrtime();
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"}); node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
var url = nodeUrl || msg.url; var url = nodeUrl || msg.url;
@ -62,12 +62,14 @@ module.exports = function(RED) {
} }
if (!url) { if (!url) {
node.error(RED._("httpin.errors.no-url"),msg); node.error(RED._("httpin.errors.no-url"),msg);
nodeDone();
return; return;
} }
// url must start http:// or https:// so assume http:// if not set // url must start http:// or https:// so assume http:// if not set
if (url.indexOf("://") !== -1 && url.indexOf("http") !== 0) { if (url.indexOf("://") !== -1 && url.indexOf("http") !== 0) {
node.warn(RED._("httpin.errors.invalid-transport")); node.warn(RED._("httpin.errors.invalid-transport"));
node.status({fill:"red",shape:"ring",text:"httpin.errors.invalid-transport"}); node.status({fill:"red",shape:"ring",text:"httpin.errors.invalid-transport"});
nodeDone();
return; return;
} }
if (!((url.indexOf("http://") === 0) || (url.indexOf("https://") === 0))) { if (!((url.indexOf("http://") === 0) || (url.indexOf("https://") === 0))) {
@ -261,10 +263,12 @@ module.exports = function(RED) {
} }
} catch(err) { } catch(err) {
node.error(RED._("httpin.errors.invalid-payload"),msg); node.error(RED._("httpin.errors.invalid-payload"),msg);
nodeDone();
return; return;
} }
} else { } else {
node.error(RED._("httpin.errors.invalid-payload"),msg); node.error(RED._("httpin.errors.invalid-payload"),msg);
nodeDone();
return; return;
} }
} }
@ -320,7 +324,8 @@ module.exports = function(RED) {
} }
msg.payload = err.toString() + " : " + url; msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code; msg.statusCode = err.code;
node.send(msg); nodeSend(msg);
nodeDone();
}else{ }else{
msg.statusCode = res.statusCode; msg.statusCode = res.statusCode;
msg.headers = res.headers; msg.headers = res.headers;
@ -354,7 +359,8 @@ module.exports = function(RED) {
} }
} }
node.status({}); node.status({});
node.send(msg); nodeSend(msg);
nodeDone();
} }
}); });
}); });

View File

@ -309,7 +309,7 @@ module.exports = function(RED) {
node.status(status); node.status(status);
}); });
} }
this.on("input", function(msg) { this.on("input", function(msg, nodeSend, nodeDone) {
var payload; var payload;
if (this.serverConfig.wholemsg) { if (this.serverConfig.wholemsg) {
var sess; var sess;
@ -337,6 +337,7 @@ module.exports = function(RED) {
}); });
} }
} }
nodeDone();
}); });
this.on('close', function() { this.on('close', function() {
node.status({}); node.status({});

View File

@ -132,7 +132,7 @@ module.exports = function(RED) {
reconnectTimeout = setTimeout(setupTcpClient, reconnectTime); reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
} }
} else { } else {
if (node.done) { node.done(); } if (node.doneClose) { node.doneClose(); }
} }
}); });
client.on('error', function(err) { client.on('error', function(err) {
@ -142,7 +142,7 @@ module.exports = function(RED) {
setupTcpClient(); setupTcpClient();
this.on('close', function(done) { this.on('close', function(done) {
node.done = done; node.doneClose = done;
this.closing = true; this.closing = true;
if (client) { client.destroy(); } if (client) { client.destroy(); }
clearTimeout(reconnectTimeout); clearTimeout(reconnectTimeout);
@ -305,13 +305,13 @@ module.exports = function(RED) {
reconnectTimeout = setTimeout(setupTcpClient,reconnectTime); reconnectTimeout = setTimeout(setupTcpClient,reconnectTime);
} }
} else { } else {
if (node.done) { node.done(); } if (node.doneClose) { node.doneClose(); }
} }
}); });
} }
setupTcpClient(); setupTcpClient();
node.on("input", function(msg) { node.on("input", function(msg,nodeSend,nodeDone) {
if (node.connected && msg.payload != null) { if (node.connected && msg.payload != null) {
if (Buffer.isBuffer(msg.payload)) { if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload); client.write(msg.payload);
@ -325,10 +325,11 @@ module.exports = function(RED) {
if (client) { node.status({}); client.destroy(); } if (client) { node.status({}); client.destroy(); }
} }
} }
nodeDone();
}); });
node.on("close", function(done) { node.on("close", function(done) {
node.done = done; node.doneClose = done;
this.closing = true; this.closing = true;
if (client) { client.destroy(); } if (client) { client.destroy(); }
clearTimeout(reconnectTimeout); clearTimeout(reconnectTimeout);
@ -337,7 +338,7 @@ module.exports = function(RED) {
} }
else if (node.beserver == "reply") { else if (node.beserver == "reply") {
node.on("input",function(msg) { node.on("input",function(msg, nodeSend, nodeDone) {
if (msg._session && msg._session.type == "tcp") { if (msg._session && msg._session.type == "tcp") {
var client = connectionPool[msg._session.id]; var client = connectionPool[msg._session.id];
if (client) { if (client) {
@ -361,6 +362,7 @@ module.exports = function(RED) {
} }
} }
} }
nodeDone();
}); });
} }
else { else {
@ -389,7 +391,7 @@ module.exports = function(RED) {
}); });
}); });
node.on("input", function(msg) { node.on("input", function(msg, nodeSend, nodeDone) {
if (msg.payload != null) { if (msg.payload != null) {
var buffer; var buffer;
if (Buffer.isBuffer(msg.payload)) { if (Buffer.isBuffer(msg.payload)) {
@ -404,6 +406,7 @@ module.exports = function(RED) {
else { connectedSockets[i].write(buffer); } else { connectedSockets[i].write(buffer); }
} }
} }
nodeDone();
}); });
server.on('error', function(err) { server.on('error', function(err) {
@ -461,7 +464,7 @@ module.exports = function(RED) {
var clients = {}; var clients = {};
this.on("input", function(msg) { this.on("input", function(msg, nodeSend, nodeDone) {
var i = 0; var i = 0;
if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) { if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
msg.payload = msg.payload.toString(); msg.payload = msg.payload.toString();
@ -483,7 +486,7 @@ module.exports = function(RED) {
connected: false, connected: false,
connecting: false connecting: false
}; };
enqueue(clients[connection_id].msgQueue, msg); enqueue(clients[connection_id].msgQueue, {msg:msg,nodeSend:nodeSend, nodeDone: nodeDone});
clients[connection_id].lastMsg = msg; clients[connection_id].lastMsg = msg;
if (!clients[connection_id].connecting && !clients[connection_id].connected) { if (!clients[connection_id].connecting && !clients[connection_id].connected) {
@ -505,9 +508,10 @@ module.exports = function(RED) {
if (clients[connection_id] && clients[connection_id].client) { if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].connected = true; clients[connection_id].connected = true;
clients[connection_id].connecting = false; clients[connection_id].connecting = false;
let msg; let event;
while (msg = dequeue(clients[connection_id].msgQueue)) { while (event = dequeue(clients[connection_id].msgQueue)) {
clients[connection_id].client.write(msg.payload); clients[connection_id].client.write(event.msg.payload);
event.nodeDone();
} }
if (node.out === "time" && node.splitc < 0) { if (node.out === "time" && node.splitc < 0) {
clients[connection_id].connected = clients[connection_id].connecting = false; clients[connection_id].connected = clients[connection_id].connecting = false;
@ -527,7 +531,7 @@ module.exports = function(RED) {
if (clients[connection_id]) { if (clients[connection_id]) {
const msg = clients[connection_id].lastMsg || {}; const msg = clients[connection_id].lastMsg || {};
msg.payload = data; msg.payload = data;
node.send(RED.util.cloneMessage(msg)); nodeSend(RED.util.cloneMessage(msg));
} }
} }
// else if (node.splitc === 0) { // else if (node.splitc === 0) {
@ -550,7 +554,7 @@ module.exports = function(RED) {
const msg = clients[connection_id].lastMsg || {}; const msg = clients[connection_id].lastMsg || {};
msg.payload = Buffer.alloc(i+1); msg.payload = Buffer.alloc(i+1);
buf.copy(msg.payload,0,0,i+1); buf.copy(msg.payload,0,0,i+1);
node.send(msg); nodeSend(msg);
if (clients[connection_id].client) { if (clients[connection_id].client) {
node.status({}); node.status({});
clients[connection_id].client.destroy(); clients[connection_id].client.destroy();
@ -572,7 +576,7 @@ module.exports = function(RED) {
const msg = clients[connection_id].lastMsg || {}; const msg = clients[connection_id].lastMsg || {};
msg.payload = Buffer.alloc(i); msg.payload = Buffer.alloc(i);
buf.copy(msg.payload,0,0,i); buf.copy(msg.payload,0,0,i);
node.send(msg); nodeSend(msg);
if (clients[connection_id].client) { if (clients[connection_id].client) {
node.status({}); node.status({});
clients[connection_id].client.destroy(); clients[connection_id].client.destroy();
@ -591,7 +595,7 @@ module.exports = function(RED) {
const msg = clients[connection_id].lastMsg || {}; const msg = clients[connection_id].lastMsg || {};
msg.payload = Buffer.alloc(i); msg.payload = Buffer.alloc(i);
buf.copy(msg.payload,0,0,i); buf.copy(msg.payload,0,0,i);
node.send(msg); nodeSend(msg);
if (clients[connection_id].client) { if (clients[connection_id].client) {
node.status({}); node.status({});
clients[connection_id].client.destroy(); clients[connection_id].client.destroy();
@ -628,9 +632,9 @@ module.exports = function(RED) {
break; break;
} }
} }
if (node.done && !anyConnected) { if (node.doneClose && !anyConnected) {
clients = {}; clients = {};
node.done(); node.doneClose();
} }
}); });
@ -663,13 +667,15 @@ module.exports = function(RED) {
} }
else if (!clients[connection_id].connecting && clients[connection_id].connected) { else if (!clients[connection_id].connecting && clients[connection_id].connected) {
if (clients[connection_id] && clients[connection_id].client) { if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].client.write(dequeue(clients[connection_id].msgQueue).payload); let event = dequeue(clients[connection_id].msgQueue)
clients[connection_id].client.write(event.msg.payload);
event.nodeDone();
} }
} }
}); });
this.on("close", function(done) { this.on("close", function(done) {
node.done = done; node.doneClose = done;
for (var cl in clients) { for (var cl in clients) {
if (clients[cl].hasOwnProperty("client")) { if (clients[cl].hasOwnProperty("client")) {
clients[cl].client.destroy(); clients[cl].client.destroy();

View File

@ -223,16 +223,19 @@ module.exports = function(RED) {
udpInputPortsInUse[p] = sock; udpInputPortsInUse[p] = sock;
} }
node.on("input", function(msg) { node.on("input", function(msg, nodeSend, nodeDone) {
if (msg.hasOwnProperty("payload")) { if (msg.hasOwnProperty("payload")) {
var add = node.addr || msg.ip || ""; var add = node.addr || msg.ip || "";
var por = node.port || msg.port || 0; var por = node.port || msg.port || 0;
if (add === "") { if (add === "") {
node.warn(RED._("udp.errors.ip-notset")); node.warn(RED._("udp.errors.ip-notset"));
nodeDone();
} else if (por === 0) { } else if (por === 0) {
node.warn(RED._("udp.errors.port-notset")); node.warn(RED._("udp.errors.port-notset"));
nodeDone();
} else if (isNaN(por) || (por < 1) || (por > 65535)) { } else if (isNaN(por) || (por < 1) || (por > 65535)) {
node.warn(RED._("udp.errors.port-invalid")); node.warn(RED._("udp.errors.port-invalid"));
nodeDone();
} else { } else {
var message; var message;
if (node.base64) { if (node.base64) {
@ -247,6 +250,7 @@ module.exports = function(RED) {
node.error("udp : "+err,msg); node.error("udp : "+err,msg);
} }
message = null; message = null;
nodeDone();
}); });
} }
} }

View File

@ -328,13 +328,14 @@ module.exports = function(RED) {
} }
if (valid) { if (valid) {
this.on('input', function(msg) { this.on('input', function(msg, send, done) {
applyRules(msg, 0, (err,msg) => { applyRules(msg, 0, (err,msg) => {
if (err) { if (err) {
node.error(err,msg); node.error(err,msg);
} else if (msg) { } else if (msg) {
node.send(msg); send(msg);
} }
done();
}) })
}); });
} }

View File

@ -34,8 +34,9 @@ module.exports = function(RED) {
} }
return data.toString(); return data.toString();
} }
function FileNode(n) { function FileNode(n) {
// Write/delete a file
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.filename = n.filename; this.filename = n.filename;
this.appendNewline = n.appendNewline; this.appendNewline = n.appendNewline;
@ -48,7 +49,7 @@ module.exports = function(RED) {
node.closing = false; node.closing = false;
node.closeCallback = null; node.closeCallback = null;
function processMsg(msg, done) { function processMsg(msg,nodeSend, done) {
var filename = node.filename || msg.filename || ""; var filename = node.filename || msg.filename || "";
if ((!node.filename) && (!node.tout)) { if ((!node.filename) && (!node.tout)) {
node.tout = setTimeout(function() { node.tout = setTimeout(function() {
@ -68,7 +69,7 @@ module.exports = function(RED) {
if (RED.settings.verbose) { if (RED.settings.verbose) {
node.log(RED._("file.status.deletedfile",{file:filename})); node.log(RED._("file.status.deletedfile",{file:filename}));
} }
node.send(msg); nodeSend(msg);
} }
done(); done();
}); });
@ -101,7 +102,7 @@ module.exports = function(RED) {
}); });
wstream.on("open", function() { wstream.on("open", function() {
wstream.end(buf, function() { wstream.end(buf, function() {
node.send(msg); nodeSend(msg);
done(); done();
}); });
}) })
@ -150,13 +151,13 @@ module.exports = function(RED) {
if (node.filename) { if (node.filename) {
// Static filename - write and reuse the stream next time // Static filename - write and reuse the stream next time
node.wstream.write(buf, function() { node.wstream.write(buf, function() {
node.send(msg); nodeSend(msg);
done(); done();
}); });
} else { } else {
// Dynamic filename - write and close the stream // Dynamic filename - write and close the stream
node.wstream.end(buf, function() { node.wstream.end(buf, function() {
node.send(msg); nodeSend(msg);
delete node.wstream; delete node.wstream;
delete node.wstreamIno; delete node.wstreamIno;
done(); done();
@ -169,12 +170,13 @@ module.exports = function(RED) {
} }
} }
function processQ(queue) { function processQueue(queue) {
var msg = queue[0]; var event = queue[0];
processMsg(msg, function() { processMsg(event.msg, event.send, function() {
event.done();
queue.shift(); queue.shift();
if (queue.length > 0) { if (queue.length > 0) {
processQ(queue); processQueue(queue);
} }
else if (node.closing) { else if (node.closing) {
closeNode(); closeNode();
@ -182,14 +184,19 @@ module.exports = function(RED) {
}); });
} }
this.on("input", function(msg) { this.on("input", function(msg,nodeSend,nodeDone) {
var msgQueue = node.msgQueue; var msgQueue = node.msgQueue;
if (msgQueue.push(msg) > 1) { msgQueue.push({
msg: msg,
send: nodeSend,
done: nodeDone
})
if (msgQueue.length > 1) {
// pending write exists // pending write exists
return; return;
} }
try { try {
processQ(msgQueue); processQueue(msgQueue);
} }
catch (e) { catch (e) {
node.msgQueue = []; node.msgQueue = [];
@ -234,6 +241,7 @@ module.exports = function(RED) {
function FileInNode(n) { function FileInNode(n) {
// Read a file
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.filename = n.filename; this.filename = n.filename;
this.format = n.format; this.format = n.format;
@ -248,13 +256,14 @@ module.exports = function(RED) {
if (this.format === "stream") { this.chunk = true; } if (this.format === "stream") { this.chunk = true; }
var node = this; var node = this;
this.on("input",function(msg) { this.on("input",function(msg, nodeSend, nodeDone) {
var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,''); var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,'');
if (!node.filename) { if (!node.filename) {
node.status({fill:"grey",shape:"dot",text:filename}); node.status({fill:"grey",shape:"dot",text:filename});
} }
if (filename === "") { if (filename === "") {
node.warn(RED._("file.errors.nofilename")); node.warn(RED._("file.errors.nofilename"));
nodeDone();
} }
else { else {
msg.filename = filename; msg.filename = filename;
@ -288,7 +297,7 @@ module.exports = function(RED) {
parts:{index:count, ch:ch, type:type, id:msg._msgid} parts:{index:count, ch:ch, type:type, id:msg._msgid}
} }
count += 1; count += 1;
node.send(m); nodeSend(m);
} }
spare = bits[i]; spare = bits[i];
} }
@ -304,7 +313,7 @@ module.exports = function(RED) {
getout = false; getout = false;
m.parts.count = count; m.parts.count = count;
} }
node.send(m); nodeSend(m);
} }
} }
else { else {
@ -318,8 +327,9 @@ module.exports = function(RED) {
var sendMessage = RED.util.cloneMessage(msg); var sendMessage = RED.util.cloneMessage(msg);
delete sendMessage.payload; delete sendMessage.payload;
sendMessage.error = err; sendMessage.error = err;
node.send(sendMessage); nodeSend(sendMessage);
} }
nodeDone();
}) })
.on('end', function() { .on('end', function() {
if (node.chunk === false) { if (node.chunk === false) {
@ -327,7 +337,7 @@ module.exports = function(RED) {
msg.payload = decode(lines, node.encoding); msg.payload = decode(lines, node.encoding);
} }
else { msg.payload = lines; } else { msg.payload = lines; }
node.send(msg); nodeSend(msg);
} }
else if (node.format === "lines") { else if (node.format === "lines") {
var m = { payload: spare, var m = { payload: spare,
@ -339,12 +349,13 @@ module.exports = function(RED) {
id: msg._msgid id: msg._msgid
} }
}; };
node.send(m); nodeSend(m);
} }
else if (getout) { // last chunk same size as high water mark - have to send empty extra packet. else if (getout) { // last chunk same size as high water mark - have to send empty extra packet.
var m = { parts:{index:count, count:count, ch:ch, type:type, id:msg._msgid} }; var m = { parts:{index:count, count:count, ch:ch, type:type, id:msg._msgid} };
node.send(m); nodeSend(m);
} }
nodeDone();
}); });
} }
}); });

View File

@ -0,0 +1,29 @@
<!--
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.
-->
<script type="text/x-red" data-help-name="complete">
<p>Trigger a flow when another node completes its handling of a message.</p>
<h3>Details</h3>
<p>If a node tells the runtime when it has finished handling a message,
this node can be used to trigger a second flow.</p>
<p>For example, this can be used alongside a node with no output port,
such as the Email sending node, to continue the flow.</p>
<p>This node must be configured to handle the event for selected nodes in the
flow. Unlike the Catch node, it does not provide a 'handle all' mode automatically
applies to all nodes in the flow.</p>
<p>Not all nodes will trigger this event - it will depend on whether they
have been implemented to support this feature as introduced in Node-RED 1.0.</p>
</script>

View File

@ -112,6 +112,9 @@
"selected": "selected nodes" "selected": "selected nodes"
} }
}, },
"complete": {
"completeNodes": "complete: __number__"
},
"debug": { "debug": {
"output": "Output", "output": "Output",
"none": "None", "none": "None",

View File

@ -18,15 +18,27 @@ var util = require("util");
var EventEmitter = require("events").EventEmitter; var EventEmitter = require("events").EventEmitter;
var redUtil = require("@node-red/util").util; var redUtil = require("@node-red/util").util;
var Log = require("@node-red/util").log; // TODO: separate module var Log = require("@node-red/util").log;
var context = require("./context"); var context = require("./context");
var flows = require("./flows"); var flows = require("./flows");
const NOOP_SEND = function() {}
/**
* The Node object is the heart of a Node-RED flow. It is the object that all
* nodes extend.
*
* The Node object itself inherits from EventEmitter, although it provides
* custom implementations of some of the EE functions in order to handle
* `input` and `close` events properly.
*/
function Node(n) { function Node(n) {
this.id = n.id; this.id = n.id;
this.type = n.type; this.type = n.type;
this.z = n.z; this.z = n.z;
this._closeCallbacks = []; this._closeCallbacks = [];
this._inputCallback = null;
this._inputCallbacks = null;
if (n.name) { if (n.name) {
this.name = n.name; this.name = n.name;
@ -43,12 +55,31 @@ function Node(n) {
// as part of its constructure - config._flow will overwrite this._flow // as part of its constructure - config._flow will overwrite this._flow
// which we can tolerate as they are the same object. // which we can tolerate as they are the same object.
Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true }) Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true })
this._asyncDelivery = n._flow.asyncMessageDelivery;
}
if (this._asyncDelivery === undefined) {
this._asyncDelivery = true;
} }
this.updateWires(n.wires); this.updateWires(n.wires);
} }
util.inherits(Node, EventEmitter); util.inherits(Node, EventEmitter);
/**
* Update the wiring configuration for this node.
*
* We try to optimise the message handling path. To do this there are three
* cases to consider:
* 1. this node is wired to nothing. In this case we replace node.send with a
* NO-OP function.
* 2. this node is wired to one other node. In this case we set `this._wire`
* as a reference to the node it is wired to. This means we avoid unnecessary
* iterations over what would otherwise be a 1-element array.
* 3. this node is wired to multiple things. The normal node.send processing of
* this.wires applies.
*
* @param {array} wires the new wiring configuration
*/
Node.prototype.updateWires = function(wires) { Node.prototype.updateWires = function(wires) {
//console.log("UPDATE",this.id); //console.log("UPDATE",this.id);
this.wires = wires || []; this.wires = wires || [];
@ -61,7 +92,7 @@ Node.prototype.updateWires = function(wires) {
this._wireCount = wc; this._wireCount = wc;
if (wc === 0) { if (wc === 0) {
// With nothing wired to the node, no-op send // With nothing wired to the node, no-op send
this.send = function(msg) {} this.send = NOOP_SEND
} else { } else {
this.send = Node.prototype.send; this.send = Node.prototype.send;
if (this.wires.length === 1 && this.wires[0].length === 1) { if (this.wires.length === 1 && this.wires[0].length === 1) {
@ -72,6 +103,13 @@ Node.prototype.updateWires = function(wires) {
} }
} }
/**
* Get the context object for this node.
*
* As most nodes do not use context, this is a lazy function that will only
* create a context instance for the node if it is needed.
* @return {object} the context object
*/
Node.prototype.context = function() { Node.prototype.context = function() {
if (!this._context) { if (!this._context) {
this._context = context.get(this._alias||this.id,this.z); this._context = context.get(this._alias||this.id,this.z);
@ -79,29 +117,194 @@ Node.prototype.context = function() {
return this._context; return this._context;
} }
/**
* Handle the complete event for a message
*
* @param {object} msg The message that has completed
* @param {error} error (optional) an error hit whilst handling the message
*/
Node.prototype._complete = function(msg,error) {
if (error) {
// For now, delegate this to this.error
// But at some point, the timeout handling will need to know about
// this as well.
this.error(error,msg);
} else {
this._flow.handleComplete(this,msg);
}
}
/**
* An internal reference to the original EventEmitter.on() function
*/
Node.prototype._on = Node.prototype.on; Node.prototype._on = Node.prototype.on;
/**
* Register a callback function for a named event.
* 'close' and 'input' events are handled locally, other events defer to EventEmitter.on()
*/
Node.prototype.on = function(event, callback) { Node.prototype.on = function(event, callback) {
var node = this; var node = this;
if (event == "close") { if (event == "close") {
this._closeCallbacks.push(callback); this._closeCallbacks.push(callback);
} else if (event === "input") {
if (this._inputCallback) {
this._inputCallbacks = [this._inputCallback, callback];
this._inputCallback = null;
} else if (this._inputCallbacks) {
this._inputCallbacks.push(callback);
} else {
this._inputCallback = callback;
}
} else { } else {
this._on(event, callback); this._on(event, callback);
} }
}; };
/**
* An internal reference to the original EventEmitter.emit() function
*/
Node.prototype._emit = Node.prototype.emit;
/**
* Emit an event to all registered listeners.
*/
Node.prototype.emit = function(event,arg) {
var node = this;
if (event === "input") {
// When Pluggable Message Routing arrives, this will be called from
// that and will already be sync/async depending on the router.
if (this._asyncDelivery) {
setImmediate(function() {
node._emitInput(arg);
});
} else {
this._emitInput(arg);
}
} else {
this._emit(event,arg);
}
}
/**
* Handle the 'input' event.
*
* This will call all registered handlers for the 'input' event.
*/
Node.prototype._emitInput = function(arg) {
var node = this;
if (node._inputCallback) {
// Just one callback registered.
try {
node._inputCallback(
arg,
function() { node.send.apply(node,arguments) },
function(err) { node._complete(arg,err); }
);
} catch(err) {
node.error(err,arg);
}
} else if (node._inputCallbacks) {
// Multiple callbacks registered. Call each one, tracking eventual completion
var c = node._inputCallbacks.length;
for (var i=0;i<c;i++) {
var cb = node._inputCallbacks[i];
if (cb.length === 2) {
c++;
}
try {
node._inputCallbacks[i](
arg,
function() { node.send.apply(node,arguments) },
function(err) {
c--;
if (c === 0) {
node._complete(arg,err);
}
}
);
} catch(err) {
node.error(err,msg);
}
}
}
}
/**
* An internal reference to the original EventEmitter.removeListener() function
*/
Node.prototype._removeListener = Node.prototype.removeListener;
/**
* Remove a listener for an event
*/
Node.prototype.removeListener = function(name, listener) {
var index;
if (name === "input") {
if (this._inputCallback && this._inputCallback === listener) {
// Removing the only callback
this._inputCallback = null;
} else if (this._inputCallbacks) {
// Removing one of many callbacks
index = this._inputCallbacks.indexOf(listener);
if (index > -1) {
this._inputCallbacks.splice(index,1);
}
// Check if we can optimise back to a single callback
if (this._inputCallbacks.length === 1) {
this._inputCallback = this._inputCallbacks[0];
this._inputCallbacks = null;
}
}
} else if (name === "close") {
index = this._closeCallbacks.indexOf(listener);
if (index > -1) {
this._closeCallbacks.splice(index,1);
}
} else {
this._removeListener(name, listener);
}
}
/**
* An internal reference to the original EventEmitter.removeAllListeners() function
*/
Node.prototype._removeAllListeners = Node.prototype.removeAllListeners;
/**
* Remove all listeners for an event
*/
Node.prototype.removeAllListeners = function(name) {
if (name === "input") {
this._inputCallback = null;
this._inputCallbacks = null;
} else if (name === "close") {
this._closeCallbacks = [];
} else {
this._removeAllListeners(name);
}
}
/**
* Called when the node is being stopped
* @param {boolean} removed Whether the node has been removed, or just being stopped
* @return {Promise} resolves when the node has closed
*/
Node.prototype.close = function(removed) { Node.prototype.close = function(removed) {
//console.log(this.type,this.id,removed); //console.log(this.type,this.id,removed);
var promises = []; var promises = [];
var node = this; var node = this;
// Call all registered close callbacks.
for (var i=0;i<this._closeCallbacks.length;i++) { for (var i=0;i<this._closeCallbacks.length;i++) {
var callback = this._closeCallbacks[i]; var callback = this._closeCallbacks[i];
if (callback.length > 0) { if (callback.length > 0) {
// The callback takes a 'done' callback and (maybe) the removed flag
promises.push( promises.push(
new Promise((resolve) => { new Promise((resolve) => {
try { try {
var args = []; var args = [];
if (callback.length === 2) { if (callback.length === 2) {
// The listener expects the removed flag
args.push(!!removed); args.push(!!removed);
} }
args.push(() => { args.push(() => {
@ -116,6 +319,7 @@ Node.prototype.close = function(removed) {
}) })
); );
} else { } else {
// No done callback so handle synchronously
try { try {
callback.call(node); callback.call(node);
} catch(err) { } catch(err) {
@ -138,6 +342,12 @@ Node.prototype.close = function(removed) {
} }
}; };
/**
* Send a message to the nodes wired.
*
*
* @param {object} msg A message or array of messages to send
*/
Node.prototype.send = function(msg) { Node.prototype.send = function(msg) {
var msgSent = false; var msgSent = false;
var node; var node;
@ -225,6 +435,12 @@ Node.prototype.send = function(msg) {
} }
}; };
/**
* Receive a message.
*
* This will emit the `input` event with the provided message.
* As of 1.0, this will return *before* any 'input' callback handler is invoked.
*/
Node.prototype.receive = function(msg) { Node.prototype.receive = function(msg) {
if (!msg) { if (!msg) {
msg = {}; msg = {};
@ -233,11 +449,7 @@ Node.prototype.receive = function(msg) {
msg._msgid = redUtil.generateId(); msg._msgid = redUtil.generateId();
} }
this.metric("receive",msg); this.metric("receive",msg);
try { this.emit("input",msg);
this.emit("input", msg);
} catch(err) {
this.error(err,msg);
}
}; };
function log_helper(self, level, msg) { function log_helper(self, level, msg) {
@ -258,15 +470,23 @@ function log_helper(self, level, msg) {
} }
Log.log(o); Log.log(o);
} }
/**
* Log an INFO level message
*/
Node.prototype.log = function(msg) { Node.prototype.log = function(msg) {
log_helper(this, Log.INFO, msg); log_helper(this, Log.INFO, msg);
}; };
/**
* Log a WARN level message
*/
Node.prototype.warn = function(msg) { Node.prototype.warn = function(msg) {
log_helper(this, Log.WARN, msg); log_helper(this, Log.WARN, msg);
}; };
/**
* Log an ERROR level message
*/
Node.prototype.error = function(logMessage,msg) { Node.prototype.error = function(logMessage,msg) {
if (typeof logMessage != 'boolean') { if (typeof logMessage != 'boolean') {
logMessage = logMessage || ""; logMessage = logMessage || "";
@ -280,15 +500,22 @@ Node.prototype.error = function(logMessage,msg) {
} }
}; };
/**
* Log an DEBUG level message
*/
Node.prototype.debug = function(msg) { Node.prototype.debug = function(msg) {
log_helper(this, Log.DEBUG, msg); log_helper(this, Log.DEBUG, msg);
} }
/**
* Log an TRACE level message
*/
Node.prototype.trace = function(msg) { Node.prototype.trace = function(msg) {
log_helper(this, Log.TRACE, msg); log_helper(this, Log.TRACE, msg);
} }
/** /**
* Log a metric event.
* If called with no args, returns whether metric collection is enabled * If called with no args, returns whether metric collection is enabled
*/ */
Node.prototype.metric = function(eventname, msg, metricValue) { Node.prototype.metric = function(eventname, msg, metricValue) {
@ -305,6 +532,8 @@ Node.prototype.metric = function(eventname, msg, metricValue) {
} }
/** /**
* Set the node's status object
*
* status: { fill:"red|green", shape:"dot|ring", text:"blah" } * status: { fill:"red|green", shape:"dot|ring", text:"blah" }
* or * or
* status: "simple text status" * status: "simple text status"

View File

@ -23,6 +23,7 @@ var Subflow;
var Log; var Log;
var nodeCloseTimeout = 15000; var nodeCloseTimeout = 15000;
var asyncMessageDelivery = true;
/** /**
* This class represents a flow within the runtime. It is responsible for * This class represents a flow within the runtime. It is responsible for
@ -125,6 +126,7 @@ class Flow {
var id; var id;
this.catchNodes = []; this.catchNodes = [];
this.statusNodes = []; this.statusNodes = [];
this.completeNodeMap = {};
var configNodes = Object.keys(this.flow.configs); var configNodes = Object.keys(this.flow.configs);
var configNodeAttempts = {}; var configNodeAttempts = {};
@ -228,7 +230,7 @@ class Flow {
this.trace(" id | type | alias"); this.trace(" id | type | alias");
this.trace("------------------|--------------|-----------------"); this.trace("------------------|--------------|-----------------");
} }
// Build the map of catch/status nodes. // Build the map of catch/status/complete nodes.
for (id in this.activeNodes) { for (id in this.activeNodes) {
if (this.activeNodes.hasOwnProperty(id)) { if (this.activeNodes.hasOwnProperty(id)) {
node = this.activeNodes[id]; node = this.activeNodes[id];
@ -237,6 +239,13 @@ class Flow {
this.catchNodes.push(node); this.catchNodes.push(node);
} else if (node.type === "status") { } else if (node.type === "status") {
this.statusNodes.push(node); this.statusNodes.push(node);
} else if (node.type === "complete") {
if (node.scope) {
node.scope.forEach(id => {
this.completeNodeMap[id] = this.completeNodeMap[id] || [];
this.completeNodeMap[id].push(node);
})
}
} }
} }
} }
@ -516,6 +525,20 @@ class Flow {
return handled; return handled;
} }
handleComplete(node,msg) {
if (this.completeNodeMap[node.id]) {
let toSend = msg;
this.completeNodeMap[node.id].forEach((completeNode,index) => {
toSend = redUtil.cloneMessage(msg);
completeNode.receive(toSend);
})
}
}
get asyncMessageDelivery() {
return asyncMessageDelivery
}
dump() { dump() {
console.log("==================") console.log("==================")
console.log(this.TYPE, this.id); console.log(this.TYPE, this.id);
@ -562,6 +585,7 @@ function stopNode(node,removed) {
module.exports = { module.exports = {
init: function(runtime) { init: function(runtime) {
nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000; nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000;
asyncMessageDelivery = !runtime.settings.runtimeSyncDelivery
Log = runtime.log; Log = runtime.log;
Subflow = require("./Subflow"); Subflow = require("./Subflow");
Subflow.init(runtime); Subflow.init(runtime);

View File

@ -729,6 +729,10 @@ module.exports = {
updateFlow: updateFlow, updateFlow: updateFlow,
removeFlow: removeFlow, removeFlow: removeFlow,
disableFlow:null, disableFlow:null,
enableFlow:null enableFlow:null,
isDeliveryModeAsync: function() {
// If settings is null, this is likely being run by unit tests
return !settings || !settings.runtimeSyncDelivery
}
}; };

View File

@ -268,45 +268,50 @@ describe('function node', function() {
helper.load(functionNode, flow, function() { helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload:"foo",topic: "bar"}); n1.receive({payload:"foo",topic: "bar"});
try { setTimeout(function() {
helper.log().called.should.be.true(); try {
var logEvents = helper.log().args.filter(function(evt) { helper.log().called.should.be.true();
return evt[0].type == "function"; var logEvents = helper.log().args.filter(function(evt) {
}); return evt[0].type == "function";
logEvents.should.have.length(1); });
var msg = logEvents[0][0]; logEvents.should.have.length(1);
msg.should.have.property('level', helper.log().ERROR); var msg = logEvents[0][0];
msg.should.have.property('id', 'n1'); msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('type', 'function'); msg.should.have.property('id', 'n1');
msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 2, col 1)'); msg.should.have.property('type', 'function');
done(); msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 2, col 1)');
} catch(err) { done();
done(err); } catch(err) {
} done(err);
}
},50);
}); });
}); });
it('should handle node.on()', function(done) { it('should handle node.on()', function(done) {
var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"node.on('close',function(){node.log('closed')});"}]; var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"node.on('close',function(){ node.log('closed')});"}];
helper.load(functionNode, flow, function() { helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload:"foo",topic: "bar"}); n1.receive({payload:"foo",topic: "bar"});
helper.getNode("n1").close(); setTimeout(function() {
try { n1.close().then(function() {
helper.log().called.should.be.true(); try {
var logEvents = helper.log().args.filter(function(evt) { helper.log().called.should.be.true();
return evt[0].type == "function"; var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "function";
});
logEvents.should.have.length(1);
var msg = logEvents[0][0];
msg.should.have.property('level', helper.log().INFO);
msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function');
msg.should.have.property('msg', 'closed');
done();
} catch(err) {
done(err);
}
}); });
logEvents.should.have.length(1); },1500);
var msg = logEvents[0][0];
msg.should.have.property('level', helper.log().INFO);
msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function');
msg.should.have.property('msg', 'closed');
done();
} catch(err) {
done(err);
}
}); });
}); });
@ -532,22 +537,24 @@ describe('function node', function() {
}); });
function checkCallbackError(name, done) { function checkCallbackError(name, done) {
try { setTimeout(function() {
helper.log().called.should.be.true(); try {
var logEvents = helper.log().args.filter(function (evt) { helper.log().called.should.be.true();
return evt[0].type == "function"; var logEvents = helper.log().args.filter(function (evt) {
}); return evt[0].type == "function";
logEvents.should.have.length(1); });
var msg = logEvents[0][0]; logEvents.should.have.length(1);
msg.should.have.property('level', helper.log().ERROR); var msg = logEvents[0][0];
msg.should.have.property('id', name); msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('type', 'function'); msg.should.have.property('id', name);
msg.should.have.property('msg', 'Error: Callback must be a function'); msg.should.have.property('type', 'function');
done(); msg.should.have.property('msg', 'Error: Callback must be a function');
} done();
catch (e) { }
done(e); catch (e) {
} done(e);
}
},50);
} }
it('should get persistable node context (w/o callback)', function(done) { it('should get persistable node context (w/o callback)', function(done) {
@ -1267,12 +1274,12 @@ describe('function node', function() {
n1.receive({payload:"foo",topic: "bar"}); n1.receive({payload:"foo",topic: "bar"});
} else { } else {
msg.should.have.property('payload', "hello"); msg.should.have.property('payload', "hello");
delete process.env._TEST_FOO_;
done(); done();
} }
} catch(err) { } catch(err) {
done(err);
} finally {
delete process.env._TEST_FOO_; delete process.env._TEST_FOO_;
done(err);
} }
}); });
n1.receive({payload:"foo",topic: "bar"}); n1.receive({payload:"foo",topic: "bar"});
@ -1285,21 +1292,23 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
try { setTimeout(function() {
helper.log().called.should.be.true(); try {
var logEvents = helper.log().args.filter(function (evt) { helper.log().called.should.be.true();
return evt[0].type == "function"; var logEvents = helper.log().args.filter(function (evt) {
}); return evt[0].type == "function";
logEvents.should.have.length(1); });
var msg = logEvents[0][0]; logEvents.should.have.length(1);
msg.should.have.property('level', helper.log().INFO); var msg = logEvents[0][0];
msg.should.have.property('id', 'n1'); msg.should.have.property('level', helper.log().INFO);
msg.should.have.property('type', 'function'); msg.should.have.property('id', 'n1');
msg.should.have.property('msg', 'test'); msg.should.have.property('type', 'function');
done(); msg.should.have.property('msg', 'test');
} catch (err) { done();
done(err); } catch (err) {
} done(err);
}
},50);
}); });
}); });
it('should log a Debug Message', function (done) { it('should log a Debug Message', function (done) {
@ -1307,21 +1316,23 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
try { setTimeout(function() {
helper.log().called.should.be.true(); try {
var logEvents = helper.log().args.filter(function (evt) { helper.log().called.should.be.true();
return evt[0].type == "function"; var logEvents = helper.log().args.filter(function (evt) {
}); return evt[0].type == "function";
logEvents.should.have.length(1); });
var msg = logEvents[0][0]; logEvents.should.have.length(1);
msg.should.have.property('level', helper.log().DEBUG); var msg = logEvents[0][0];
msg.should.have.property('id', 'n1'); msg.should.have.property('level', helper.log().DEBUG);
msg.should.have.property('type', 'function'); msg.should.have.property('id', 'n1');
msg.should.have.property('msg', 'test'); msg.should.have.property('type', 'function');
done(); msg.should.have.property('msg', 'test');
} catch (err) { done();
done(err); } catch (err) {
} done(err);
}
},50);
}); });
}); });
it('should log a Trace Message', function (done) { it('should log a Trace Message', function (done) {
@ -1329,21 +1340,23 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
try { setTimeout(function() {
helper.log().called.should.be.true(); try {
var logEvents = helper.log().args.filter(function (evt) { helper.log().called.should.be.true();
return evt[0].type == "function"; var logEvents = helper.log().args.filter(function (evt) {
}); return evt[0].type == "function";
logEvents.should.have.length(1); });
var msg = logEvents[0][0]; logEvents.should.have.length(1);
msg.should.have.property('level', helper.log().TRACE); var msg = logEvents[0][0];
msg.should.have.property('id', 'n1'); msg.should.have.property('level', helper.log().TRACE);
msg.should.have.property('type', 'function'); msg.should.have.property('id', 'n1');
msg.should.have.property('msg', 'test'); msg.should.have.property('type', 'function');
done(); msg.should.have.property('msg', 'test');
} catch (err) { done();
done(err); } catch (err) {
} done(err);
}
},50);
}); });
}); });
it('should log a Warning Message', function (done) { it('should log a Warning Message', function (done) {
@ -1351,21 +1364,23 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
try { setTimeout(function() {
helper.log().called.should.be.true(); try {
var logEvents = helper.log().args.filter(function (evt) { helper.log().called.should.be.true();
return evt[0].type == "function"; var logEvents = helper.log().args.filter(function (evt) {
}); return evt[0].type == "function";
logEvents.should.have.length(1); });
var msg = logEvents[0][0]; logEvents.should.have.length(1);
msg.should.have.property('level', helper.log().WARN); var msg = logEvents[0][0];
msg.should.have.property('id', 'n1'); msg.should.have.property('level', helper.log().WARN);
msg.should.have.property('type', 'function'); msg.should.have.property('id', 'n1');
msg.should.have.property('msg', 'test'); msg.should.have.property('type', 'function');
done(); msg.should.have.property('msg', 'test');
} catch (err) { done();
done(err); } catch (err) {
} done(err);
}
},50);
}); });
}); });
it('should log an Error Message', function (done) { it('should log an Error Message', function (done) {
@ -1373,21 +1388,23 @@ describe('function node', function() {
helper.load(functionNode, flow, function () { helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"}); n1.receive({payload: "foo", topic: "bar"});
try { setTimeout(function() {
helper.log().called.should.be.true(); try {
var logEvents = helper.log().args.filter(function (evt) { helper.log().called.should.be.true();
return evt[0].type == "function"; var logEvents = helper.log().args.filter(function (evt) {
}); return evt[0].type == "function";
logEvents.should.have.length(1); });
var msg = logEvents[0][0]; logEvents.should.have.length(1);
msg.should.have.property('level', helper.log().ERROR); var msg = logEvents[0][0];
msg.should.have.property('id', 'n1'); msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('type', 'function'); msg.should.have.property('id', 'n1');
msg.should.have.property('msg', 'test'); msg.should.have.property('type', 'function');
done(); msg.should.have.property('msg', 'test');
} catch (err) { done();
done(err); } catch (err) {
} done(err);
}
},50);
}); });
}); });
it('should catch thrown string', function (done) { it('should catch thrown string', function (done) {

View File

@ -475,20 +475,21 @@ describe('SORT node', function() {
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(sortNode, flow, function() { helper.load(sortNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "sort";
});
var evt = logEvents[0][0];
evt.should.have.property('id', "n1");
evt.should.have.property('type', "sort");
evt.should.have.property('msg', "sort.clear");
done();
}, 150);
var msg = { payload: 0, var msg = { payload: 0,
parts: { id: "X", index: 0, count: 2} }; parts: { id: "X", index: 0, count: 2} };
n1.receive(msg); n1.receive(msg);
n1.close(); setTimeout(function() {
n1.close().then(function() {
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "sort";
});
var evt = logEvents[0][0];
evt.should.have.property('id', "n1");
evt.should.have.property('type', "sort");
evt.should.have.property('msg', "sort.clear");
done();
});
}, 150);
}); });
}); });

View File

@ -21,7 +21,7 @@ var fs = require('fs-extra');
var htmlNode = require("nr-test-utils").require("@node-red/nodes/core/parsers/70-HTML.js"); var htmlNode = require("nr-test-utils").require("@node-red/nodes/core/parsers/70-HTML.js");
var helper = require("node-red-node-test-helper"); var helper = require("node-red-node-test-helper");
describe('html node', function() { describe('HTML node', function() {
var resourcesDir = __dirname+ path.sep + ".." + path.sep + ".." + path.sep + ".." + path.sep + "resources" + path.sep; var resourcesDir = __dirname+ path.sep + ".." + path.sep + ".." + path.sep + ".." + path.sep + "resources" + path.sep;
var file = path.join(resourcesDir, "70-HTML-test-file.html"); var file = path.join(resourcesDir, "70-HTML-test-file.html");
@ -228,16 +228,20 @@ describe('html node', function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n1.receive({payload:null,topic: "bar"}); n1.receive({payload:null,topic: "bar"});
helper.log().called.should.be.true(); setTimeout(function() {
var logEvents = helper.log().args.filter(function(evt) { try {
return evt[0].type == "html"; helper.log().called.should.be.true();
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "html";
// Each logEvent is the array of args passed to the function. });
logEvents[0][0].should.have.a.property('msg'); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); // Each logEvent is the array of args passed to the function.
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done(); done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }

View File

@ -148,14 +148,18 @@ describe('JSON node', function() {
var jn1 = helper.getNode("jn1"); var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2"); var jn2 = helper.getNode("jn2");
jn1.receive({payload:'foo',topic: "bar"}); jn1.receive({payload:'foo',topic: "bar"});
var logEvents = helper.log().args.filter(function(evt) { setTimeout(function() {
return evt[0].type == "json"; try {
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "json";
logEvents[0][0].should.have.a.property('msg'); });
logEvents[0][0].msg.should.startWith("Unexpected token o"); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('msg');
done(); logEvents[0][0].msg.should.startWith("Unexpected token o");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done();
} catch(err) { done(err) }
},20);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -378,14 +382,18 @@ describe('JSON node', function() {
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
var obj = {"number": "foo", "string": 3}; var obj = {"number": "foo", "string": 3};
jn1.receive({payload:obj, schema:schema}); jn1.receive({payload:obj, schema:schema});
var logEvents = helper.log().args.filter(function(evt) { setTimeout(function() {
return evt[0].type == "json"; try {
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "json";
logEvents[0][0].should.have.a.property('msg'); });
logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('msg');
done(); logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -402,14 +410,18 @@ describe('JSON node', function() {
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
var obj = {"number": "foo", "string": 3}; var obj = {"number": "foo", "string": 3};
jn1.receive({payload:obj, schema:schema}); jn1.receive({payload:obj, schema:schema});
var logEvents = helper.log().args.filter(function(evt) { setTimeout(function() {
return evt[0].type == "json"; try {
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "json";
logEvents[0][0].should.have.a.property('msg'); });
logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('msg');
done(); logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -426,14 +438,18 @@ describe('JSON node', function() {
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
var jsonString = '{"number":"Hello","string":3}'; var jsonString = '{"number":"Hello","string":3}';
jn1.receive({payload:jsonString, schema:schema}); jn1.receive({payload:jsonString, schema:schema});
var logEvents = helper.log().args.filter(function(evt) { setTimeout(function() {
return evt[0].type == "json"; try {
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "json";
logEvents[0][0].should.have.a.property('msg'); });
logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('msg');
done(); logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -450,14 +466,18 @@ describe('JSON node', function() {
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
var jsonString = '{"number":"Hello","string":3}'; var jsonString = '{"number":"Hello","string":3}';
jn1.receive({payload:jsonString, schema:schema}); jn1.receive({payload:jsonString, schema:schema});
var logEvents = helper.log().args.filter(function(evt) { setTimeout(function() {
return evt[0].type == "json"; try {
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "json";
logEvents[0][0].should.have.a.property('msg'); });
logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('msg');
done(); logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }
@ -474,14 +494,18 @@ describe('JSON node', function() {
var schema = "garbage"; var schema = "garbage";
var obj = {"number": "foo", "string": 3}; var obj = {"number": "foo", "string": 3};
jn1.receive({payload:obj, schema:schema}); jn1.receive({payload:obj, schema:schema});
var logEvents = helper.log().args.filter(function(evt) { setTimeout(function() {
return evt[0].type == "json"; try {
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "json";
logEvents[0][0].should.have.a.property('msg'); });
logEvents[0][0].msg.should.equal("json.errors.schema-error-compile"); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('msg');
done(); logEvents[0][0].msg.should.equal("json.errors.schema-error-compile");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }

View File

@ -130,14 +130,18 @@ describe('YAML node', function() {
var yn1 = helper.getNode("yn1"); var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2"); var yn2 = helper.getNode("yn2");
yn1.receive({payload:'employees:\n-firstName: John\n- lastName: Smith\n',topic: "bar"}); yn1.receive({payload:'employees:\n-firstName: John\n- lastName: Smith\n',topic: "bar"});
var logEvents = helper.log().args.filter(function(evt) { setTimeout(function() {
return evt[0].type == "yaml"; try {
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "yaml";
logEvents[0][0].should.have.a.property('msg'); });
logEvents[0][0].msg.should.startWith("end of the stream"); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('level',helper.log().ERROR); logEvents[0][0].should.have.a.property('msg');
done(); logEvents[0][0].msg.should.startWith("end of the stream");
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
done();
} catch(err) { done(err) }
},50);
} catch(err) { } catch(err) {
done(err); done(err);
} }

View File

@ -156,10 +156,50 @@ describe('Node', function() {
throw new Error("test error"); throw new Error("test error");
}); });
n.receive(message); n.receive(message);
n.error.called.should.be.true(); setTimeout(function() {
n.error.firstCall.args[1].should.equal(message); n.error.called.should.be.true();
done(); n.error.firstCall.args[1].should.equal(message);
done();
},50);
});
it('calls parent flow handleComplete when callback provided', function(done) {
var n = new RedNode({id:'123',type:'abc', _flow: {
handleComplete: function(node,msg) {
try {
msg.should.deepEqual(message);
done();
} catch(err) {
done(err);
}
}
}});
var message = {payload:"hello world"};
n.on('input',function(msg, nodeSend, nodeDone) {
nodeDone();
});
n.receive(message);
});
it('logs error if callback provides error', function(done) {
var n = new RedNode({id:'123',type:'abc'});
sinon.stub(n,"error",function(err,msg) {});
var message = {payload:"hello world"};
n.on('input',function(msg, nodeSend, nodeDone) {
nodeDone(new Error("test error"));
setTimeout(function() {
try {
n.error.called.should.be.true();
n.error.firstCall.args[0].toString().should.equal("Error: test error");
n.error.firstCall.args[1].should.equal(message);
done();
} catch(err) {
done(err);
}
},50);
});
n.receive(message);
}); });
}); });
@ -172,15 +212,69 @@ describe('Node', function() {
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]}); var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'}); var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
var message = {payload:"hello world"}; var message = {payload:"hello world"};
var messageReceived = false;
n2.on('input',function(msg) { n2.on('input',function(msg) {
// msg equals message, and is not a new copy // msg equals message, and is not a new copy
messageReceived = true;
should.deepEqual(msg,message); should.deepEqual(msg,message);
should.strictEqual(msg,message); should.strictEqual(msg,message);
done(); done();
}); });
n1.send(message); n1.send(message);
messageReceived.should.be.false();
});
it('emits a single message - synchronous mode', function(done) {
var flow = {
getNode: (id) => { return {'n1':n1,'n2':n2}[id]},
asyncMessageDelivery: false
};
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
var message = {payload:"hello world"};
var messageReceived = false;
var notSyncErr;
n2.on('input',function(msg) {
try {
// msg equals message, and is not a new copy
messageReceived = true;
should.deepEqual(msg,message);
should.strictEqual(msg,message);
done(notSyncErr);
} catch(err) {
done(err);
}
});
n1.send(message);
try {
messageReceived.should.be.true();
} catch(err) {
notSyncErr = err;
}
});
it('emits a message with callback provided send', function(done) {
var flow = {
getNode: (id) => { return {'n1':n1,'n2':n2}[id]},
handleComplete: (node,msg) => {}
};
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
var message = {payload:"hello world"};
var messageReceived = false;
n1.on('input',function(msg,nodeSend,nodeDone) {
nodeSend(msg);
nodeDone();
});
n2.on('input',function(msg) {
// msg equals message, and is not a new copy
messageReceived = true;
should.deepEqual(msg,message);
should.strictEqual(msg,message);
done();
});
n1.receive(message);
messageReceived.should.be.false();
}); });
it('emits multiple messages on a single output', function(done) { it('emits multiple messages on a single output', function(done) {
@ -356,12 +450,13 @@ describe('Node', function() {
it("logs the uuid for all messages sent", function(done) { it("logs the uuid for all messages sent", function(done) {
var logHandler = { var logHandler = {
msgIds:[],
messagesSent: 0, messagesSent: 0,
emit: function(event, msg) { emit: function(event, msg) {
if (msg.event == "node.abc.send" && msg.level == Log.METRIC) { if (msg.event == "node.abc.send" && msg.level == Log.METRIC) {
this.messagesSent++; this.messagesSent++;
this.msgIds.push(msg.msgid);
(typeof msg.msgid).should.not.be.equal("undefined"); (typeof msg.msgid).should.not.be.equal("undefined");
done();
} }
} }
}; };
@ -375,6 +470,17 @@ describe('Node', function() {
var receiver1 = new RedNode({_flow:flow,id:'n2',type:'abc'}); var receiver1 = new RedNode({_flow:flow,id:'n2',type:'abc'});
var receiver2 = new RedNode({_flow:flow,id:'n3',type:'abc'}); var receiver2 = new RedNode({_flow:flow,id:'n3',type:'abc'});
sender.send({"some": "message"}); sender.send({"some": "message"});
setTimeout(function() {
try {
logHandler.messagesSent.should.equal(1);
should.exist(logHandler.msgIds[0])
Log.removeHandler(logHandler);
done();
} catch(err) {
Log.removeHandler(logHandler);
done(err);
}
},50)
}) })
}); });

View File

@ -122,6 +122,7 @@ describe('Flow', function() {
currentNodes[node.id] = node; currentNodes[node.id] = node;
this.on('input',function(msg) { this.on('input',function(msg) {
node.handled++; node.handled++;
msg.handled = node.handled;
node.messages.push(msg); node.messages.push(msg);
node.send(msg); node.send(msg);
}); });
@ -136,12 +137,42 @@ describe('Flow', function() {
} }
util.inherits(TestAsyncNode,Node); util.inherits(TestAsyncNode,Node);
var TestDoneNode = function(n) {
Node.call(this,n);
var node = this;
this.scope = n.scope;
this.uncaught = n.uncaught;
this.foo = n.foo;
this.handled = 0;
this.messages = [];
this.stopped = false;
this.closeDelay = n.closeDelay || 50;
currentNodes[node.id] = node;
this.on('input',function(msg, send, done) {
node.handled++;
node.messages.push(msg);
send(msg);
done();
});
this.on('close',function(done) {
setTimeout(function() {
node.stopped = true;
stoppedNodes[node.id] = node;
delete currentNodes[node.id];
done();
},node.closeDelay);
});
}
util.inherits(TestDoneNode,Node);
before(function() { before(function() {
getType = sinon.stub(typeRegistry,"get",function(type) { getType = sinon.stub(typeRegistry,"get",function(type) {
if (type=="test") { if (type=="test") {
return TestNode; return TestNode;
} else if (type=="testError"){ } else if (type=="testError"){
return TestErrorNode; return TestErrorNode;
} else if (type=="testDone"){
return TestDoneNode;
} else { } else {
return TestAsyncNode; return TestAsyncNode;
} }
@ -189,28 +220,28 @@ describe('Flow', function() {
currentNodes["2"].should.have.a.property("handled",0); currentNodes["2"].should.have.a.property("handled",0);
currentNodes["3"].should.have.a.property("handled",0); currentNodes["3"].should.have.a.property("handled",0);
currentNodes["3"].on("input", function() {
currentNodes["1"].should.have.a.property("handled",1);
currentNodes["2"].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["1"].receive({payload:"test"}); flow.stop().then(function() {
try {
currentNodes["1"].should.have.a.property("handled",1); currentNodes.should.not.have.a.property("1");
currentNodes["2"].should.have.a.property("handled",1); currentNodes.should.not.have.a.property("2");
currentNodes["3"].should.have.a.property("handled",1); currentNodes.should.not.have.a.property("3");
currentNodes.should.not.have.a.property("4");
flow.stop().then(function() { stoppedNodes.should.have.a.property("1");
try { stoppedNodes.should.have.a.property("2");
currentNodes.should.not.have.a.property("1"); stoppedNodes.should.have.a.property("3");
currentNodes.should.not.have.a.property("2"); stoppedNodes.should.have.a.property("4");
currentNodes.should.not.have.a.property("3"); done();
currentNodes.should.not.have.a.property("4"); } catch(err) {
stoppedNodes.should.have.a.property("1"); done(err);
stoppedNodes.should.have.a.property("2"); }
stoppedNodes.should.have.a.property("3"); });
stoppedNodes.should.have.a.property("4");
done();
} catch(err) {
done(err);
}
}); });
currentNodes["1"].receive({payload:"test"});
}); });
it("instantiates config nodes in the right order",function(done) { it("instantiates config nodes in the right order",function(done) {
@ -350,25 +381,27 @@ describe('Flow', function() {
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
currentNodes["1"].should.have.a.property("handled",1); setTimeout(function() {
// Message doesn't reach 3 as 2 is disabled currentNodes["1"].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",0); // Message doesn't reach 3 as 2 is disabled
currentNodes["3"].should.have.a.property("handled",0);
flow.stop().then(function() { flow.stop().then(function() {
try { try {
currentNodes.should.not.have.a.property("1"); currentNodes.should.not.have.a.property("1");
currentNodes.should.not.have.a.property("2"); currentNodes.should.not.have.a.property("2");
currentNodes.should.not.have.a.property("3"); currentNodes.should.not.have.a.property("3");
currentNodes.should.not.have.a.property("4"); currentNodes.should.not.have.a.property("4");
stoppedNodes.should.have.a.property("1"); stoppedNodes.should.have.a.property("1");
stoppedNodes.should.not.have.a.property("2"); stoppedNodes.should.not.have.a.property("2");
stoppedNodes.should.have.a.property("3"); stoppedNodes.should.have.a.property("3");
stoppedNodes.should.have.a.property("4"); stoppedNodes.should.have.a.property("4");
done(); done();
} catch(err) { } catch(err) {
done(err); done(err);
} }
}); });
},50);
}); });
}); });
@ -551,31 +584,34 @@ describe('Flow', function() {
flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status",random:"otherProperty"}); flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status",random:"otherProperty"});
currentNodes["sn"].should.have.a.property("handled",1); setTimeout(function() {
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); currentNodes["sn"].should.have.a.property("handled",1);
statusMessage.status.should.have.a.property("text","my-status"); var statusMessage = currentNodes["sn"].messages[0];
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","1");
statusMessage.status.source.should.have.a.property("type","test");
statusMessage.status.source.should.have.a.property("name","a");
currentNodes["sn2"].should.have.a.property("handled",1); statusMessage.should.have.a.property("status");
statusMessage = currentNodes["sn2"].messages[0]; statusMessage.status.should.have.a.property("text","my-status");
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","1");
statusMessage.status.source.should.have.a.property("type","test");
statusMessage.status.source.should.have.a.property("name","a");
statusMessage.should.have.a.property("status"); currentNodes["sn2"].should.have.a.property("handled",1);
statusMessage.status.should.have.a.property("text","my-status"); statusMessage = currentNodes["sn2"].messages[0];
statusMessage.status.should.have.a.property("random","otherProperty");
statusMessage.status.should.have.a.property("source"); statusMessage.should.have.a.property("status");
statusMessage.status.source.should.have.a.property("id","1"); statusMessage.status.should.have.a.property("text","my-status");
statusMessage.status.source.should.have.a.property("type","test"); statusMessage.status.should.have.a.property("random","otherProperty");
statusMessage.status.source.should.have.a.property("name","a"); statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","1");
statusMessage.status.source.should.have.a.property("type","test");
statusMessage.status.source.should.have.a.property("name","a");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50)
}); });
it("passes a status event to the adjacent scoped status node ",function(done) { it("passes a status event to the adjacent scoped status node ",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -596,21 +632,23 @@ describe('Flow', function() {
flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status"}); flow.handleStatus(config.flows["t1"].nodes["1"],{text:"my-status"});
currentNodes["sn"].should.have.a.property("handled",0); setTimeout(function() {
currentNodes["sn2"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",0);
var statusMessage = currentNodes["sn2"].messages[0]; currentNodes["sn2"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn2"].messages[0];
statusMessage.should.have.a.property("status"); statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","my-status"); statusMessage.status.should.have.a.property("text","my-status");
statusMessage.status.should.have.a.property("source"); statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","1"); statusMessage.status.source.should.have.a.property("id","1");
statusMessage.status.source.should.have.a.property("type","test"); statusMessage.status.source.should.have.a.property("type","test");
statusMessage.status.source.should.have.a.property("name","a"); statusMessage.status.source.should.have.a.property("name","a");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
}); });
@ -636,33 +674,35 @@ describe('Flow', function() {
flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"}); flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"});
currentNodes["sn"].should.have.a.property("handled",1); setTimeout(function() {
var statusMessage = currentNodes["sn"].messages[0]; currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("error"); statusMessage.should.have.a.property("error");
statusMessage.error.should.have.a.property("message","my-error"); statusMessage.error.should.have.a.property("message","my-error");
statusMessage.error.should.have.a.property("source"); statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("id","1"); statusMessage.error.source.should.have.a.property("id","1");
statusMessage.error.source.should.have.a.property("type","test"); statusMessage.error.source.should.have.a.property("type","test");
statusMessage.error.source.should.have.a.property("name","a"); statusMessage.error.source.should.have.a.property("name","a");
currentNodes["sn2"].should.have.a.property("handled",1); currentNodes["sn2"].should.have.a.property("handled",1);
statusMessage = currentNodes["sn2"].messages[0]; statusMessage = currentNodes["sn2"].messages[0];
statusMessage.should.have.a.property("error"); statusMessage.should.have.a.property("error");
statusMessage.error.should.have.a.property("message","my-error"); statusMessage.error.should.have.a.property("message","my-error");
statusMessage.error.should.have.a.property("source"); statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("id","1"); statusMessage.error.source.should.have.a.property("id","1");
statusMessage.error.source.should.have.a.property("type","test"); statusMessage.error.source.should.have.a.property("type","test");
statusMessage.error.source.should.have.a.property("name","a"); statusMessage.error.source.should.have.a.property("name","a");
// Node sn3 has uncaught:true - so should not get called // Node sn3 has uncaught:true - so should not get called
currentNodes["sn3"].should.have.a.property("handled",0); currentNodes["sn3"].should.have.a.property("handled",0);
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("passes an error event to the adjacent scoped catch node ",function(done) { it("passes an error event to the adjacent scoped catch node ",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -684,39 +724,42 @@ describe('Flow', function() {
flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"}); flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo"});
currentNodes["sn"].should.have.a.property("handled",0); setTimeout(function() {
currentNodes["sn2"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",0);
var statusMessage = currentNodes["sn2"].messages[0]; currentNodes["sn2"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn2"].messages[0];
statusMessage.should.have.a.property("error"); statusMessage.should.have.a.property("error");
statusMessage.error.should.have.a.property("message","my-error"); statusMessage.error.should.have.a.property("message","my-error");
statusMessage.error.should.have.a.property("source"); statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("id","1"); statusMessage.error.source.should.have.a.property("id","1");
statusMessage.error.source.should.have.a.property("type","test"); statusMessage.error.source.should.have.a.property("type","test");
statusMessage.error.source.should.have.a.property("name","a"); statusMessage.error.source.should.have.a.property("name","a");
// Node sn3/4 have uncaught:true - so should not get called // Node sn3/4 have uncaught:true - so should not get called
currentNodes["sn3"].should.have.a.property("handled",0); currentNodes["sn3"].should.have.a.property("handled",0);
currentNodes["sn4"].should.have.a.property("handled",0); currentNodes["sn4"].should.have.a.property("handled",0);
// Inject error that sn1/2 will ignore - so should get picked up by sn3 // Inject error that sn1/2 will ignore - so should get picked up by sn3
flow.handleError(config.flows["t1"].nodes["3"],"my-error-2",{a:"foo-2"}); flow.handleError(config.flows["t1"].nodes["3"],"my-error-2",{a:"foo-2"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",0);
currentNodes["sn2"].should.have.a.property("handled",1);
currentNodes["sn3"].should.have.a.property("handled",1);
currentNodes["sn4"].should.have.a.property("handled",1);
currentNodes["sn"].should.have.a.property("handled",0); statusMessage = currentNodes["sn3"].messages[0];
currentNodes["sn2"].should.have.a.property("handled",1); statusMessage.should.have.a.property("error");
currentNodes["sn3"].should.have.a.property("handled",1); statusMessage.error.should.have.a.property("message","my-error-2");
currentNodes["sn4"].should.have.a.property("handled",1); statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("id","3");
statusMessage.error.source.should.have.a.property("type","test");
statusMessage = currentNodes["sn3"].messages[0]; flow.stop().then(function() {
statusMessage.should.have.a.property("error"); done();
statusMessage.error.should.have.a.property("message","my-error-2"); });
statusMessage.error.should.have.a.property("source"); },50);
statusMessage.error.source.should.have.a.property("id","3"); },50);
statusMessage.error.source.should.have.a.property("type","test");
flow.stop().then(function() {
done();
});
}); });
it("moves any existing error object sideways",function(done){ it("moves any existing error object sideways",function(done){
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -733,22 +776,54 @@ describe('Flow', function() {
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo",error:"existing"}); flow.handleError(config.flows["t1"].nodes["1"],"my-error",{a:"foo",error:"existing"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
currentNodes["sn"].should.have.a.property("handled",1); statusMessage.should.have.a.property("_error","existing");
var statusMessage = currentNodes["sn"].messages[0]; statusMessage.should.have.a.property("error");
statusMessage.error.should.have.a.property("message","my-error");
statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("id","1");
statusMessage.error.source.should.have.a.property("type","test");
statusMessage.error.source.should.have.a.property("name","a");
statusMessage.should.have.a.property("_error","existing"); flow.stop().then(function() {
statusMessage.should.have.a.property("error"); done();
statusMessage.error.should.have.a.property("message","my-error"); });
statusMessage.error.should.have.a.property("source"); },50);
statusMessage.error.source.should.have.a.property("id","1");
statusMessage.error.source.should.have.a.property("type","test");
statusMessage.error.source.should.have.a.property("name","a");
flow.stop().then(function() {
done();
});
}); });
it("prevents an error looping more than 10 times",function(){}); it("prevents an error looping more than 10 times",function(){});
}); });
describe("#handleComplete",function() {
it("passes a complete event to the adjacent Complete node",function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"testDone",name:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"test",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"testDone",foo:"a",wires:[]},
{id:"cn",x:10,y:10,z:"t1",type:"complete",scope:["1","3"],foo:"a",wires:[]}
]);
var flow = Flow.create({},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
Object.keys(activeNodes).should.have.length(4);
var msg = {payload: "hello world"}
var n1 = currentNodes["1"].receive(msg);
setTimeout(function() {
currentNodes["cn"].should.have.a.property("handled",2);
currentNodes["cn"].messages[0].should.have.a.property("handled",1);
currentNodes["cn"].messages[1].should.have.a.property("handled",2);
flow.stop().then(function() {
done();
});
},50);
});
});
}); });

View File

@ -271,32 +271,34 @@ describe('Subflow', function() {
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
currentNodes["1"].should.have.a.property("handled",1); setTimeout(function() {
// currentNodes[sfInstanceId].should.have.a.property("handled",1); currentNodes["1"].should.have.a.property("handled",1);
// currentNodes[sfInstanceId2].should.have.a.property("handled",1); // currentNodes[sfInstanceId].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1); // currentNodes[sfInstanceId2].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",1);
flow.stop().then(function() { flow.stop().then(function() {
Object.keys(currentNodes).should.have.length(0); Object.keys(currentNodes).should.have.length(0);
Object.keys(stoppedNodes).should.have.length(6); Object.keys(stoppedNodes).should.have.length(6);
// currentNodes.should.not.have.a.property("1"); // currentNodes.should.not.have.a.property("1");
// currentNodes.should.not.have.a.property("3"); // currentNodes.should.not.have.a.property("3");
// currentNodes.should.not.have.a.property("4"); // currentNodes.should.not.have.a.property("4");
// // currentNodes.should.not.have.a.property(sfInstanceId); // // currentNodes.should.not.have.a.property(sfInstanceId);
// // currentNodes.should.not.have.a.property(sfInstanceId2); // // currentNodes.should.not.have.a.property(sfInstanceId2);
// // currentNodes.should.not.have.a.property(sfConfigId); // // currentNodes.should.not.have.a.property(sfConfigId);
// stoppedNodes.should.have.a.property("1"); // stoppedNodes.should.have.a.property("1");
// stoppedNodes.should.have.a.property("3"); // stoppedNodes.should.have.a.property("3");
// stoppedNodes.should.have.a.property("4"); // stoppedNodes.should.have.a.property("4");
// // stoppedNodes.should.have.a.property(sfInstanceId); // // stoppedNodes.should.have.a.property(sfInstanceId);
// // stoppedNodes.should.have.a.property(sfInstanceId2); // // stoppedNodes.should.have.a.property(sfInstanceId2);
// // stoppedNodes.should.have.a.property(sfConfigId); // // stoppedNodes.should.have.a.property(sfConfigId);
done(); done();
}); });
},50);
}); });
it("instantiates a subflow inside a subflow and stops it",function(done) { it("instantiates a subflow inside a subflow and stops it",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -322,17 +324,14 @@ describe('Subflow', function() {
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
currentNodes["1"].should.have.a.property("handled",1); setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1); flow.stop().then(function() {
Object.keys(currentNodes).should.have.length(0);
done();
});
flow.stop().then(function() { },50);
Object.keys(currentNodes).should.have.length(0);
done();
});
}); });
it("rewires a subflow node on update/start",function(done){ it("rewires a subflow node on update/start",function(done){
@ -369,27 +368,31 @@ describe('Subflow', function() {
currentNodes["1"].receive({payload:"test"}); currentNodes["1"].receive({payload:"test"});
currentNodes["1"].should.have.a.property("handled",1); setTimeout(function() {
// currentNodes[sfInstanceId].should.have.a.property("handled",1); currentNodes["1"].should.have.a.property("handled",1);
// currentNodes[sfInstanceId2].should.have.a.property("handled",1); // currentNodes[sfInstanceId].should.have.a.property("handled",1);
currentNodes["3"].should.have.a.property("handled",1); // currentNodes[sfInstanceId2].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",0); currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",0);
flow.update(newConfig,newConfig.flows["t1"]); flow.update(newConfig,newConfig.flows["t1"]);
flow.start(diff) flow.start(diff)
currentNodes["1"].receive({payload:"test2"}); currentNodes["1"].receive({payload:"test2"});
setTimeout(function() {
currentNodes["1"].should.have.a.property("handled",2); currentNodes["1"].should.have.a.property("handled",2);
// currentNodes[sfInstanceId].should.have.a.property("handled",2); // currentNodes[sfInstanceId].should.have.a.property("handled",2);
// currentNodes[sfInstanceId2].should.have.a.property("handled",2); // currentNodes[sfInstanceId2].should.have.a.property("handled",2);
currentNodes["3"].should.have.a.property("handled",1); currentNodes["3"].should.have.a.property("handled",1);
currentNodes["4"].should.have.a.property("handled",1); currentNodes["4"].should.have.a.property("handled",1);
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
},50);
}); });
}); });
describe('#stop', function() { describe('#stop', function() {
@ -436,20 +439,20 @@ describe('Subflow', function() {
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
setTimeout(function() {
currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
currentNodes["sn"].should.have.a.property("handled",1); statusMessage.should.have.a.property("status");
var statusMessage = currentNodes["sn"].messages[0]; statusMessage.status.should.have.a.property("text","test status");
statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("type","testStatus");
statusMessage.status.source.should.have.a.property("name","test-status-node");
statusMessage.should.have.a.property("status"); flow.stop().then(function() {
statusMessage.status.should.have.a.property("text","test status"); done();
statusMessage.status.should.have.a.property("source"); });
statusMessage.status.source.should.have.a.property("type","testStatus"); },50);
statusMessage.status.source.should.have.a.property("name","test-status-node");
flow.stop().then(function() {
done();
});
}); });
it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) { it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -472,21 +475,23 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
parentFlowStatusCalled.should.be.false(); setTimeout(function() {
parentFlowStatusCalled.should.be.false();
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","test status"); statusMessage.status.should.have.a.property("text","test status");
statusMessage.status.should.have.a.property("source"); statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("type","testStatus"); statusMessage.status.source.should.have.a.property("type","testStatus");
statusMessage.status.source.should.have.a.property("name","test-status-node"); statusMessage.status.source.should.have.a.property("name","test-status-node");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
}); });
@ -517,19 +522,21 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:"test-payload"}); activeNodes["1"].receive({payload:"test-payload"});
currentNodes["sn"].should.have.a.property("handled",1); setTimeout(function() {
var statusMessage = currentNodes["sn"].messages[0]; currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","test-payload"); statusMessage.status.should.have.a.property("text","test-payload");
statusMessage.status.should.have.a.property("source"); statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2"); statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1"); statusMessage.status.source.should.have.a.property("type","subflow:sf1");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) { it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -557,19 +564,21 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:{text:"payload-obj"}}); activeNodes["1"].receive({payload:{text:"payload-obj"}});
currentNodes["sn"].should.have.a.property("handled",1); setTimeout(function() {
var statusMessage = currentNodes["sn"].messages[0]; currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","payload-obj"); statusMessage.status.should.have.a.property("text","payload-obj");
statusMessage.status.should.have.a.property("source"); statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2"); statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1"); statusMessage.status.source.should.have.a.property("type","subflow:sf1");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) { it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -597,19 +606,21 @@ describe('Subflow', function() {
activeNodes["1"].receive({status:{text:"status-obj"}}); activeNodes["1"].receive({status:{text:"status-obj"}});
currentNodes["sn"].should.have.a.property("handled",1); setTimeout(function() {
var statusMessage = currentNodes["sn"].messages[0]; currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("status"); statusMessage.should.have.a.property("status");
statusMessage.status.should.have.a.property("text","status-obj"); statusMessage.status.should.have.a.property("text","status-obj");
statusMessage.status.should.have.a.property("source"); statusMessage.status.should.have.a.property("source");
statusMessage.status.source.should.have.a.property("id","2"); statusMessage.status.source.should.have.a.property("id","2");
statusMessage.status.source.should.have.a.property("type","subflow:sf1"); statusMessage.status.source.should.have.a.property("type","subflow:sf1");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("does not emit a regular status event if it contains a subflow-status node", function(done) { it("does not emit a regular status event if it contains a subflow-status node", function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -666,18 +677,20 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
currentNodes["sn"].should.have.a.property("handled",1); setTimeout(function() {
var statusMessage = currentNodes["sn"].messages[0]; currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("error"); statusMessage.should.have.a.property("error");
statusMessage.error.should.have.a.property("message","test error"); statusMessage.error.should.have.a.property("message","test error");
statusMessage.error.should.have.a.property("source"); statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("type","testError"); statusMessage.error.source.should.have.a.property("type","testError");
statusMessage.error.source.should.have.a.property("name","test-error-node"); statusMessage.error.source.should.have.a.property("name","test-error-node");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) { it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -699,20 +712,22 @@ describe('Subflow', function() {
activeNodes["1"].receive({payload:"test"}); activeNodes["1"].receive({payload:"test"});
parentFlowErrorCalled.should.be.false(); setTimeout(function() {
parentFlowErrorCalled.should.be.false();
currentNodes["sn"].should.have.a.property("handled",1); currentNodes["sn"].should.have.a.property("handled",1);
var statusMessage = currentNodes["sn"].messages[0]; var statusMessage = currentNodes["sn"].messages[0];
statusMessage.should.have.a.property("error"); statusMessage.should.have.a.property("error");
statusMessage.error.should.have.a.property("message","test error"); statusMessage.error.should.have.a.property("message","test error");
statusMessage.error.should.have.a.property("source"); statusMessage.error.should.have.a.property("source");
statusMessage.error.source.should.have.a.property("type","testError"); statusMessage.error.source.should.have.a.property("type","testError");
statusMessage.error.source.should.have.a.property("name","test-error-node"); statusMessage.error.source.should.have.a.property("name","test-error-node");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
}); });
}); });
@ -756,11 +771,13 @@ describe('Subflow', function() {
process.env["__KEY__"] = "__VAL__"; process.env["__KEY__"] = "__VAL__";
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
currentNodes["3"].should.have.a.property("received", "__VAL__"); setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL__");
flow.stop().then(function() {
done(); flow.stop().then(function() {
}); done();
});
},50);
}); });
it("can access subflow env var", function(done) { it("can access subflow env var", function(done) {
@ -792,13 +809,15 @@ describe('Subflow', function() {
} }
process.env["__KEY__"] = "__VAL0__"; process.env["__KEY__"] = "__VAL0__";
setEnv(testenv_node, "__KEY__", "__VAL1__"); setEnv(testenv_node, "__KEY__", "__VAL1__");
currentNodes["1"].receive({payload: "test"});
currentNodes["3"].should.have.a.property("received", "__VAL1__");
flow.stop().then(function() { currentNodes["1"].receive({payload: "test"});
done(); setTimeout(function() {
}); currentNodes["3"].should.have.a.property("received", "__VAL1__");
flow.stop().then(function() {
done();
});
},50);
}); });
it("can access nested subflow env var", function(done) { it("can access nested subflow env var", function(done) {
@ -840,21 +859,27 @@ describe('Subflow', function() {
process.env["__KEY__"] = "__VAL0__"; process.env["__KEY__"] = "__VAL0__";
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
currentNodes["3"].should.have.a.property("received", "__VAL0__"); setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL0__");
setEnv(node_sf1_1, "__KEY__", "__VAL1__"); setEnv(node_sf1_1, "__KEY__", "__VAL1__");
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
currentNodes["3"].should.have.a.property("received", "__VAL1__"); setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL1__");
setEnv(node_sf2_1, "__KEY__", "__VAL2__"); setEnv(node_sf2_1, "__KEY__", "__VAL2__");
currentNodes["1"].receive({payload: "test"}); currentNodes["1"].receive({payload: "test"});
currentNodes["3"].should.have.a.property("received", "__VAL2__"); setTimeout(function() {
currentNodes["3"].should.have.a.property("received", "__VAL2__");
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
}); });
},50);
},50);
},50);
}); });
}); });
}); });