Merge branch 'master' into 0.16

This commit is contained in:
Nick O'Leary 2017-01-06 14:30:13 +00:00 committed by GitHub
commit e73216d4c1
27 changed files with 354 additions and 160 deletions

View File

@ -1,3 +1,38 @@
#### 0.15.3: Maintenance Release
- Tcpgetfix: Another small check (#1070)
- TCPGet: Ensure done() is called only once (#1068)
- Allow $ and _ at start of property identifiers Fixes #1063
- TCPGet: Separated the node.connected property for each instance (#1062)
- Corrected 'overide' typo in XML node help (#1061)
- TCPGet: Last property check (hopefully) (#1059)
- Add additional safety checks to avoid acting on non-existent objects (#1057)
- add --title for process name to command line options
- add indicator for fire once on inject node
- reimplement $(env var) replace to share common code.
- Fix error message for missing node html file, and add test.
- Let credentials also use $(...) substitutions from ENV
- Rename insecureRedirect to requireHttps
- Add setting to cause insecure redirect (#1054)
- Palette editor fixes (#1033)
- Close comms on stopServer in test helper (#1020)
- Tcpgetfix (#1050)
- TCPget: Store incoming messages alongside the client object to keep reference
- Merge remote-tracking branch 'upstream/master' into tcpgetfix
- TCPget can now handle concurrent sessions (#1042)
- Better scope handling
- Add security checks
- small change to udp httpadmin
- Fix comparison to "" in tcpin
- Change scope of clients object
- Works when connection is left open
- First release of multi connection tcpget
- Fix node.error() not printing when passed false (#1037)
- fix test for CSV array input
- different test for Pi (rather than use serial port name)
- Fix missing 0 handling for css node with array input
#### 0.15.2: Maintenance Release
- Revert bidi changes to nodes and hide menu option until fixed Fixes #1024

View File

@ -38,21 +38,13 @@ If you want to raise a pull-request with a new feature, or a refactoring
of existing code, it may well get rejected if you haven't discussed it on
the [mailing list](https://groups.google.com/forum/#!forum/node-red) first.
### Contributor License Agreement
All contributors need to sign the JS Foundation's Contributor License Agreement.
It is an online process and quick to do. You can read the details of the agreement
here: https://cla.js.foundation/node-red/node-red.
In order for us to accept pull-requests, the contributor must first complete
a Contributor License Agreement (CLA). This clarifies the intellectual
property license granted with any contribution. It is for your protection as a
Contributor as well as the protection of IBM and its customers; it does not
change your rights to use your own Contributions for any other purpose.
If you raise a pull-request without having signed the CLA, you will be prompted
to do so automatically.
You can download the CLAs here:
- [individual](http://nodered.org/cla/node-red-cla-individual.pdf)
- [corporate](http://nodered.org/cla/node-red-cla-corporate.pdf)
If you are an IBMer, please contact us directly as the contribution process is
slightly different.
### Coding standards

View File

@ -14,9 +14,6 @@
* limitations under the License.
**/
(function($) {
var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
flow: {value:"flow",label:"flow.",validate:RED.utils.validatePropertyExpression},

View File

@ -255,7 +255,9 @@ RED.palette.editor = (function() {
nodeEntry.removeButton.hide();
} else {
nodeEntry.enableButton.removeClass('disabled');
nodeEntry.removeButton.show();
if (moduleInfo.local) {
nodeEntry.removeButton.show();
}
if (activeTypeCount === 0) {
nodeEntry.enableButton.html(RED._('palette.editor.enableall'));
} else {

View File

@ -201,6 +201,11 @@
icon: "inject.png",
label: function() {
var suffix = "";
// if fire once then add small indication
if (this.once) {
suffix = " ¹";
}
// but replace with repeat one if set to repeat
if (this.repeat || this.crontab) {
suffix = " ↻";
}

View File

@ -23,9 +23,9 @@ module.exports = function(RED) {
var gpioCommand = __dirname+'/nrgpio';
try {
fs.statSync("/dev/ttyAMA0"); // unlikely if not on a Pi
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); }
} catch(err) {
//RED.log.info(RED._("rpi-gpio.errors.ignorenode"));
throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
}

View File

@ -83,7 +83,7 @@ module.exports = function(RED) {
}
});
client.on('end', function() {
if (!node.stream || (node.datatype == "utf8" && node.newline != "" && buffer.length > 0)) {
if (!node.stream || (node.datatype == "utf8" && node.newline !== "" && buffer.length > 0)) {
var msg = {topic:node.topic, payload:buffer};
msg._session = {type:"tcp",id:id};
if (buffer.length !== 0) {
@ -407,69 +407,88 @@ module.exports = function(RED) {
} // jshint ignore:line
}
var buf;
if (this.out == "count") {
if (this.splitc === 0) { buf = new Buffer(1); }
else { buf = new Buffer(this.splitc); }
}
else { buf = new Buffer(65536); } // set it to 64k... hopefully big enough for most TCP packets.... but only hopefully
this.connected = false;
var node = this;
var client;
var m;
var clients = {};
this.on("input", function(msg) {
m = msg;
var i = 0;
if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
msg.payload = msg.payload.toString();
}
if (!node.connected) {
client = net.Socket();
if (socketTimeout !== null) { client.setTimeout(socketTimeout); }
var host = node.server || msg.host;
var port = node.port || msg.port;
var host = node.server || msg.host;
var port = node.port || msg.port;
// Store client information independently
// the clients object will have:
// clients[id].client, clients[id].msg, clients[id].timeout
var connection_id = host + ":" + port;
clients[connection_id] = clients[connection_id] || {};
clients[connection_id].msg = msg;
clients[connection_id].connected = clients[connection_id].connected || false;
if (!clients[connection_id].connected) {
var buf;
if (this.out == "count") {
if (this.splitc === 0) { buf = new Buffer(1); }
else { buf = new Buffer(this.splitc); }
}
else { buf = new Buffer(65536); } // set it to 64k... hopefully big enough for most TCP packets.... but only hopefully
clients[connection_id].client = net.Socket();
if (socketTimeout !== null) { clients[connection_id].client.setTimeout(socketTimeout);}
if (host && port) {
client.connect(port, host, function() {
clients[connection_id].client.connect(port, host, function() {
//node.log(RED._("tcpin.errors.client-connected"));
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
node.connected = true;
client.write(msg.payload);
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].connected = true;
clients[connection_id].client.write(clients[connection_id].msg.payload);
}
});
}
else {
node.warn(RED._("tcpin.errors.no-host"));
}
client.on('data', function(data) {
clients[connection_id].client.on('data', function(data) {
if (node.out == "sit") { // if we are staying connected just send the buffer
m.payload = data;
node.send(m);
if (clients[connection_id]) {
clients[connection_id].msg.payload = data;
node.send(clients[connection_id].msg);
}
}
else if (node.splitc === 0) {
msg.payload = data;
node.send(msg);
clients[connection_id].msg.payload = data;
node.send(clients[connection_id].msg);
}
else {
for (var j = 0; j < data.length; j++ ) {
if (node.out === "time") {
// do the timer thing
if (node.tout) {
i += 1;
buf[i] = data[j];
}
else {
node.tout = setTimeout(function () {
node.tout = null;
msg.payload = new Buffer(i+1);
buf.copy(msg.payload,0,0,i+1);
node.send(msg);
if (client) { node.status({}); client.destroy(); }
}, node.splitc);
i = 0;
buf[0] = data[j];
if (clients[connection_id]) {
// do the timer thing
if (clients[connection_id].timeout) {
i += 1;
buf[i] = data[j];
}
else {
clients[connection_id].timeout = setTimeout(function () {
if (clients[connection_id]) {
clients[connection_id].timeout = null;
clients[connection_id].msg.payload = new Buffer(i+1);
buf.copy(clients[connection_id].msg.payload,0,0,i+1);
node.send(clients[connection_id].msg);
if (clients[connection_id].client) {
node.status({}); clients[connection_id].client.destroy();
delete clients[connection_id];
}
}
}, node.splitc);
i = 0;
buf[0] = data[j];
}
}
}
// count bytes into a buffer...
@ -477,11 +496,16 @@ module.exports = function(RED) {
buf[i] = data[j];
i += 1;
if ( i >= node.splitc) {
msg.payload = new Buffer(i);
buf.copy(msg.payload,0,0,i);
node.send(msg);
if (client) { node.status({}); client.destroy(); }
i = 0;
if (clients[connection_id]) {
clients[connection_id].msg.payload = new Buffer(i);
buf.copy(clients[connection_id].msg.payload,0,0,i);
node.send(clients[connection_id].msg);
if (clients[connection_id].client) {
node.status({}); clients[connection_id].client.destroy();
delete clients[connection_id];
}
i = 0;
}
}
}
// look for a char
@ -489,61 +513,101 @@ module.exports = function(RED) {
buf[i] = data[j];
i += 1;
if (data[j] == node.splitc) {
msg.payload = new Buffer(i);
buf.copy(msg.payload,0,0,i);
node.send(msg);
if (client) { node.status({}); client.destroy(); }
i = 0;
if (clients[connection_id]) {
clients[connection_id].msg.payload = new Buffer(i);
buf.copy(clients[connection_id].msg.payload,0,0,i);
node.send(clients[connection_id].msg);
if (clients[connection_id].client) {
node.status({}); clients[connection_id].client.destroy();
delete clients[connection_id];
}
i = 0;
}
}
}
}
}
});
client.on('end', function() {
clients[connection_id].client.on('end', function() {
//console.log("END");
node.connected = false;
node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"});
client = null;
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].connected = false;
clients[connection_id].client = null;
}
});
client.on('close', function() {
clients[connection_id].client.on('close', function() {
//console.log("CLOSE");
node.connected = false;
if (node.done) { node.done(); }
if (clients[connection_id]) {
clients[connection_id].connected = false;
}
var anyConnected = false;
for (var client in clients) {
if (clients[client].connected) {
anyConnected = true;
break;
}
}
if (node.done && !anyConnected) {
clients = {};
node.done();
}
});
client.on('error', function() {
clients[connection_id].client.on('error', function() {
//console.log("ERROR");
node.connected = false;
node.status({fill:"red",shape:"ring",text:"common.status.error"});
node.error(RED._("tcpin.errors.connect-fail"),msg);
if (client) { client.destroy(); }
node.error(RED._("tcpin.errors.connect-fail") + " " + connection_id, msg);
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].connected = false;
clients[connection_id].client.destroy();
delete clients[connection_id];
}
});
client.on('timeout',function() {
clients[connection_id].client.on('timeout',function() {
//console.log("TIMEOUT");
node.connected = false;
clients[connection_id].connected = false;
node.status({fill:"grey",shape:"dot",text:"tcpin.errors.connect-timeout"});
//node.warn(RED._("tcpin.errors.connect-timeout"));
if (client) {
client.connect(port, host, function() {
node.connected = true;
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].client.connect(port, host, function() {
clients[connection_id].connected = true;
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
}
});
}
else { client.write(msg.payload); }
else {
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].client.write(clients[connection_id].msg.payload);
}
}
});
this.on("close", function(done) {
node.done = done;
if (client) {
client.destroy();
for (var client in clients) {
clients[client].client.destroy();
}
node.status({});
if (!node.connected) { done(); }
var anyConnected = false;
for (var c in clients) {
if (clients[c].connected) {
anyConnected = true;
break;
}
}
if (!anyConnected) {
clients = {};
done();
}
});
}

View File

@ -102,7 +102,7 @@ module.exports = function(RED) {
try { server.bind(node.port,node.iface); }
catch(e) { } // Don't worry if already bound
}
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-in.read'), function(req,res) {
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-ports.read'), function(req,res) {
res.json(Object.keys(udpInputPortsInUse));
});
RED.nodes.registerType("udp in",UDPin);

View File

@ -36,7 +36,7 @@
<p>A function that parses the <code>msg.payload</code> to convert xml to/from a javascript object. Places the result in the payload.</p>
<p>If the input is a string it tries to parse it as XML and creates a javascript object.</p>
<p>If the input is a javascript object it tries to build an XML string.</p>
<p>You can also pass in a <code>msg.options</code> object to overide all the multitude of parameters. See
<p>You can also pass in a <code>msg.options</code> object to override all the multitude of parameters. See
<a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md#options" target="_blank">the xml2js docs</a>
for more information.</p>
<p>If set, options in the edit dialogue override those passed in on the msg.options object.</p>

View File

@ -1,6 +1,6 @@
{
"name" : "node-red",
"version" : "0.15.2",
"version" : "0.15.3",
"description" : "A visual tool for wiring the Internet of Things",
"homepage" : "http://nodered.org",
"license" : "Apache-2.0",

36
red.js
View File

@ -33,17 +33,20 @@ var settingsFile;
var flowFile;
var knownOpts = {
"settings":[path],
"userDir":[path],
"help": Boolean,
"port": Number,
"v": Boolean,
"help": Boolean
"settings": [path],
"title": String,
"userDir": [path],
"verbose": Boolean
};
var shortHands = {
"s":["--settings"],
"u":["--userDir"],
"?":["--help"],
"p":["--port"],
"?":["--help"]
"s":["--settings"],
"t":["--help"],
"u":["--userDir"],
"v":["--verbose"]
};
nopt.invalidHandler = function(k,v,t) {
// TODO: console.log(k,v,t);
@ -54,14 +57,15 @@ var parsedArgs = nopt(knownOpts,shortHands,process.argv,2)
if (parsedArgs.help) {
console.log("Node-RED v"+RED.version());
console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR]");
console.log(" [--port PORT] [flows.json]");
console.log(" [--port PORT] [--title TITLE] [flows.json]");
console.log("");
console.log("Options:");
console.log(" -s, --settings FILE use specified settings file");
console.log(" -u, --userDir DIR use specified user directory");
console.log(" -p, --port PORT port to listen on");
console.log(" -v enable verbose output");
console.log(" -?, --help show usage");
console.log(" -s, --settings FILE use specified settings file");
console.log(" --title TITLE process window title");
console.log(" -u, --userDir DIR use specified user directory");
console.log(" -v, --verbose enable verbose output");
console.log(" -?, --help show this help");
console.log("");
console.log("Documentation can be found at http://nodered.org");
process.exit();
@ -121,9 +125,9 @@ if (parsedArgs.v) {
}
if (settings.https) {
server = https.createServer(settings.https,function(req,res){app(req,res);});
server = https.createServer(settings.https,function(req,res) {app(req,res);});
} else {
server = http.createServer(function(req,res){app(req,res);});
server = http.createServer(function(req,res) {app(req,res);});
}
server.setMaxListeners(0);
@ -224,7 +228,6 @@ if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
if (settings.httpNodeRoot !== false) {
app.use(settings.httpNodeRoot,RED.httpNode);
}
if (settings.httpStatic) {
settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth;
if (settings.httpStaticAuth) {
@ -265,7 +268,7 @@ RED.start().then(function() {
if (settings.httpAdminRoot === false) {
RED.log.info(RED.log._("server.admin-ui-disabled"));
}
process.title = 'node-red';
process.title = parsedArgs.title || 'node-red';
RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
});
} else {
@ -280,7 +283,6 @@ RED.start().then(function() {
}
});
process.on('uncaughtException',function(err) {
util.log('[red] Uncaught Exception:');
if (err.stack) {

View File

@ -87,6 +87,16 @@ function init(_server,_runtime) {
if (!settings.disableEditor) {
ui.init(runtime);
var editorApp = express();
if (settings.requireHttps === true) {
editorApp.enable('trust proxy');
editorApp.use(function (req, res, next) {
if (req.secure) {
next();
} else {
res.redirect('https://' + req.headers.host + req.originalUrl);
}
});
}
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
editorApp.get("/icons/:icon",ui.icon);
theme.init(runtime);

View File

@ -236,7 +236,9 @@ Node.prototype.warn = function(msg) {
};
Node.prototype.error = function(logMessage,msg) {
logMessage = logMessage || "";
if (typeof logMessage != 'boolean') {
logMessage = logMessage || "";
}
log_helper(this, Log.ERROR, logMessage);
/* istanbul ignore else */
if (msg) {

View File

@ -259,32 +259,6 @@ function Flow(global,flow) {
}
}
}
}
var EnvVarPropertyRE = /^\$\((\S+)\)$/;
function mapEnvVarProperties(obj,prop) {
if (Buffer.isBuffer(obj[prop])) {
return;
} else if (Array.isArray(obj[prop])) {
for (var i=0;i<obj[prop].length;i++) {
mapEnvVarProperties(obj[prop],i);
}
} else if (typeof obj[prop] === 'string') {
var m;
if ( (m = EnvVarPropertyRE.exec(obj[prop])) !== null) {
if (process.env.hasOwnProperty(m[1])) {
obj[prop] = process.env[m[1]];
}
}
} else {
for (var p in obj[prop]) {
if (obj[prop].hasOwnProperty(p)) {
mapEnvVarProperties(obj[prop],p);
}
}
}
}
function createNode(type,config) {
@ -295,7 +269,7 @@ function createNode(type,config) {
delete conf.credentials;
for (var p in conf) {
if (conf.hasOwnProperty(p)) {
mapEnvVarProperties(conf,p);
flowUtil.mapEnvVarProperties(conf,p);
}
}
try {

View File

@ -37,9 +37,35 @@ function diffNodes(oldNode,newNode) {
return false;
}
var EnvVarPropertyRE = /^\$\((\S+)\)$/;
function mapEnvVarProperties(obj,prop) {
if (Buffer.isBuffer(obj[prop])) {
return;
} else if (Array.isArray(obj[prop])) {
for (var i=0;i<obj[prop].length;i++) {
mapEnvVarProperties(obj[prop],i);
}
} else if (typeof obj[prop] === 'string') {
var m;
if ( (m = EnvVarPropertyRE.exec(obj[prop])) !== null) {
if (process.env.hasOwnProperty(m[1])) {
obj[prop] = process.env[m[1]];
}
}
} else {
for (var p in obj[prop]) {
if (obj[prop].hasOwnProperty(p)) {
mapEnvVarProperties(obj[prop],p);
}
}
}
}
module.exports = {
diffNodes: diffNodes,
mapEnvVarProperties: mapEnvVarProperties,
parseConfig: function(config) {
var flow = {};
@ -301,7 +327,7 @@ module.exports = {
}
}
}
} while(madeChange===true)
} while (madeChange===true)
// Find any nodes that exist on a subflow template and remove from changed
// list as the parent subflow will now be marked as containing a change
@ -316,7 +342,7 @@ module.exports = {
// Recursively mark all instances of changed subflows as changed
var changedSubflowStack = Object.keys(changedSubflows);
while(changedSubflowStack.length > 0) {
while (changedSubflowStack.length > 0) {
var subflowId = changedSubflowStack.pop();
for (id in newConfig.allNodes) {
if (newConfig.allNodes.hasOwnProperty(id)) {
@ -350,7 +376,7 @@ module.exports = {
// Traverse the links of all modified nodes to mark the connected nodes
var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired);
var visited = {};
while(modifiedNodes.length > 0) {
while (modifiedNodes.length > 0) {
node = modifiedNodes.pop();
if (!visited[node]) {
visited[node] = true;

View File

@ -21,6 +21,7 @@ var fs = require("fs");
var registry = require("./registry");
var credentials = require("./credentials");
var flows = require("./flows");
var flowUtil = require("./flows/util")
var context = require("./context");
var Node = require("./Node");
var log = require("../log");
@ -69,6 +70,12 @@ function createNode(node,def) {
var creds = credentials.get(id);
if (creds) {
//console.log("Attaching credentials to ",node.id);
// allow $(foo) syntax to substitute env variables for credentials also...
for (var p in creds) {
if (creds.hasOwnProperty(p)) {
flowUtil.mapEnvVarProperties(creds,p);
}
}
node.credentials = creds;
} else if (credentials.getDefinition(node.type)) {
node.credentials = {};
@ -146,7 +153,6 @@ module.exports = {
// disableFlow: flows.disableFlow,
// enableFlow: flows.enableFlow,
// Credentials
addCredentials: credentials.add,
getCredentials: credentials.get,

View File

@ -87,10 +87,10 @@ function createNodeApi(node) {
red.server = runtime.adminApi.server;
} else {
red.comms = {
publish: function(){}
publish: function() {}
};
red.library = {
register: function(){}
register: function() {}
};
red.auth = {
needsPermission: function() {}
@ -203,7 +203,7 @@ function loadNodeConfig(fileInfo) {
if (!node.types) {
node.types = [];
}
node.err = "Error: "+file+" does not exist";
node.err = "Error: "+node.template+" does not exist";
} else {
node.types = [];
node.err = err.toString();
@ -215,7 +215,7 @@ function loadNodeConfig(fileInfo) {
var regExp = /<script ([^>]*)data-template-name=['"]([^'"]*)['"]/gi;
var match = null;
while((match = regExp.exec(content)) !== null) {
while ((match = regExp.exec(content)) !== null) {
types.push(match[2]);
}
node.types = types;
@ -226,7 +226,7 @@ function loadNodeConfig(fileInfo) {
var mainContent = "";
var helpContent = {};
var index = 0;
while((match = regExp.exec(content)) !== null) {
while ((match = regExp.exec(content)) !== null) {
mainContent += content.substring(index,regExp.lastIndex-match[1].length);
index = regExp.lastIndex;
var help = content.substring(regExp.lastIndex-match[1].length,regExp.lastIndex);
@ -382,7 +382,6 @@ function getNodeHelp(node,lang) {
} else {
node.help[lang] = node.help[runtime.i18n.defaultLang];
}
}
return node.help[lang];
}

View File

@ -281,6 +281,7 @@ function getModuleFiles(module) {
}
nodeModuleFiles.forEach(function(node) {
nodeList[moduleFile.package.name].nodes[node.name] = node;
nodeList[moduleFile.package.name].nodes[node.name].local = moduleFile.local || false;
});
});
return nodeList;

View File

@ -165,7 +165,7 @@ function normalisePropertyExpression(str) {
throw new Error("Invalid property expression: unterminated expression");
}
// Next char is a-z
if (!/[a-z0-9]/i.test(str[i+1])) {
if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;

View File

@ -129,6 +129,10 @@ module.exports = {
// cert: fs.readFileSync('certificate.pem')
//},
// The following property can be used to cause insecure HTTP connections to
// be redirected to HTTPS.
//requireHttps: true
// The following property can be used to disable the editor. The admin API
// is not affected by this option. To disable both the editor and the admin
// API, use either the httpRoot or httpAdminRoot properties

View File

@ -227,12 +227,12 @@ describe('CSV node', function() {
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload', '1,2,3,4\n');
msg.should.have.property('payload', '0,1,2,3,4\n');
done();
}
catch(e) { done(e); }
});
var testJson = [1,2,3,4];
var testJson = [0,1,2,3,4];
n1.emit("input", {payload:testJson});
});
});
@ -245,12 +245,12 @@ describe('CSV node', function() {
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload', '1,2,3,4\n4,3,2,1\n');
msg.should.have.property('payload', '0,1,2,3,4\n4,3,2,1,0\n');
done();
}
catch(e) { done(e); }
});
var testJson = [[1,2,3,4],[4,3,2,1]];
var testJson = [[0,1,2,3,4],[4,3,2,1,0]];
n1.emit("input", {payload:testJson});
});
});

View File

@ -149,6 +149,9 @@ module.exports = {
stopServer: function(done) {
if (server) {
try {
server.on('close', function() {
comms.stop();
});
server.close(done);
} catch(e) {
done();

View File

@ -34,7 +34,20 @@ describe('flows/util', function() {
getType.restore();
});
describe('#mapEnvVarProperties',function() {
it('handles ENV substitutions in an object', function() {
process.env.foo1 = "bar1";
process.env.foo2 = "bar2";
process.env.foo3 = "bar3";
var foo = {a:"$(foo1)",b:"$(foo2)",c:{d:"$(foo3)"}};
for (var p in foo) {
if (foo.hasOwnProperty(p)) {
flowUtil.mapEnvVarProperties(foo,p);
}
}
foo.should.eql({ a: 'bar1', b: 'bar2', c: { d: 'bar3' } } );
});
});
describe('#diffNodes',function() {
it('handles a null old node', function() {
@ -189,7 +202,7 @@ describe('flows/util', function() {
});
it('identifies nodes with changed properties, including upstream linked', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[1].bar = "c";
@ -207,7 +220,7 @@ describe('flows/util', function() {
});
it('identifies nodes with changed credentials, including downstream linked', function() {
var config = [{id:"1",type:"test",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var config = [{id:"1",type:"test",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[0].credentials = {};
@ -225,7 +238,7 @@ describe('flows/util', function() {
});
it('identifies nodes with changed wiring', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[1].wires[0][0] = "3";
@ -243,7 +256,7 @@ describe('flows/util', function() {
});
it('identifies nodes with changed wiring - second connection added', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[1].wires[0].push("1");
@ -261,7 +274,7 @@ describe('flows/util', function() {
});
it('identifies nodes with changed wiring - node connected', function() {
var config = [{id:"1",type:"test",foo:"a",wires:[["2"]]},{id:"2",type:"test",bar:"b",wires:[[]]},{id:"3",type:"test",foo:"a",wires:[]}];
var config = [{id:"1",type:"test",foo:"a",wires:[["2"]]},{id:"2",type:"test",bar:"b",wires:[[]]},{id:"3",type:"test",foo:"a",wires:[]}];
var newConfig = clone(config);
newConfig[1].wires.push("3");

View File

@ -38,8 +38,9 @@ describe("red/nodes/index", function() {
index.clearRegistry();
});
process.env.foo="bar";
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
var testCredentials = {"tab1":{"b":1,"c":2}};
var testCredentials = {"tab1":{"b":1, "c":"2", "d":"$(foo)"}};
var storage = {
getFlows: function() {
return when({red:123,flows:testFlows,credentials:testCredentials});
@ -58,7 +59,7 @@ describe("red/nodes/index", function() {
var runtime = {
settings: settings,
storage: storage,
log: {debug:function(){},warn:function(){}}
log: {debug:function() {}, warn:function() {}}
};
function TestNode(n) {
@ -75,7 +76,8 @@ describe("red/nodes/index", function() {
index.loadFlows().then(function() {
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
testnode.credentials.should.have.property('b',1);
testnode.credentials.should.have.property('c',2);
testnode.credentials.should.have.property('c',"2");
testnode.credentials.should.have.property('d',"bar");
done();
}).otherwise(function(err) {
done(err);

View File

@ -317,6 +317,53 @@ describe("red/nodes/registry/loader",function() {
done(err);
});
});
it("load core node files scanned by lfs - missing html file", function(done) {
stubs.push(sinon.stub(localfilesystem,"getNodeFiles", function(){
var result = {};
result["node-red"] = {
"name": "node-red",
"nodes": {
"DuffNode": {
"file": path.join(resourcesDir,"DuffNode","DuffNode.js"),
"module": "node-red",
"name": "DuffNode"
}
}
};
return result;
}));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return }));
// This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) {
registry.addNodeSet.called.should.be.true();
registry.addNodeSet.lastCall.args[0].should.eql("node-red/DuffNode");
registry.addNodeSet.lastCall.args[1].should.have.a.property('id',"node-red/DuffNode");
registry.addNodeSet.lastCall.args[1].should.have.a.property('module',"node-red");
registry.addNodeSet.lastCall.args[1].should.have.a.property('enabled',true);
registry.addNodeSet.lastCall.args[1].should.have.a.property('loaded',false);
registry.addNodeSet.lastCall.args[1].should.have.a.property('version',undefined);
registry.addNodeSet.lastCall.args[1].should.have.a.property('types');
registry.addNodeSet.lastCall.args[1].types.should.have.a.length(0);
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('config');
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('help');
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('namespace','node-red');
registry.addNodeSet.lastCall.args[1].should.have.a.property('err');
registry.addNodeSet.lastCall.args[1].err.should.endWith("DuffNode.html does not exist");
nodes.registerType.calledOnce.should.be.false();
done();
}).otherwise(function(err) {
done(err);
});
});
});
describe("#addModule",function() {

View File

@ -0,0 +1,5 @@
// A test node that exports a function
module.exports = function(RED) {
function DuffNode(n) {}
RED.nodes.registerType("duff-node",DuffNode);
}

View File

@ -337,6 +337,11 @@ describe("red/util", function() {
it("pass 'a.b'.c",function() { testABC("'a.b'.c",['a.b','c']); })
it('pass a.$b.c',function() { testABC('a.$b.c',['a','$b','c']); })
it('pass a["$b"].c',function() { testABC('a["$b"].c',['a','$b','c']); })
it('pass a._b.c',function() { testABC('a._b.c',['a','_b','c']); })
it('pass a["_b"].c',function() { testABC('a["_b"].c',['a','_b','c']); })
it("fail a'b'.c",function() { testInvalid("a'b'.c"); })
it("fail a['b'.c",function() { testInvalid("a['b'.c"); })
it("fail a[]",function() { testInvalid("a[]"); })