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

Merge pull request #498 from anna2130/nr-cli-enhancements

nr-cli enhancements - server side api
This commit is contained in:
Nick O'Leary 2014-12-05 20:39:42 +00:00
commit 14b84f0c7b
16 changed files with 856 additions and 835 deletions

View File

@ -19,7 +19,6 @@ var util = require('util');
var ui = require("./ui"); var ui = require("./ui");
var nodes = require("./nodes"); var nodes = require("./nodes");
var plugins = require("./plugins");
var flows = require("./flows"); var flows = require("./flows");
var library = require("./library"); var library = require("./library");
@ -52,20 +51,18 @@ function init(adminApp) {
adminApp.get("/nodes",nodes.getAll); adminApp.get("/nodes",nodes.getAll);
adminApp.post("/nodes",nodes.post); adminApp.post("/nodes",nodes.post);
adminApp.get("/nodes/:id",nodes.get); adminApp.get("/nodes/:mod",nodes.getModule);
adminApp.put("/nodes/:id",nodes.put); adminApp.put("/nodes/:mod",nodes.putModule);
adminApp.delete("/nodes/:id",nodes.delete); adminApp.delete("/nodes/:mod",nodes.delete);
// Plugins adminApp.get("/nodes/:mod/:set",nodes.getSet);
adminApp.get("/plugins",plugins.getAll); adminApp.put("/nodes/:mod/:set",nodes.putSet);
adminApp.get("/plugins/:id",plugins.get);
// Library // Library
adminApp.post(new RegExp("/library/flows\/(.*)"),library.post); adminApp.post(new RegExp("/library/flows\/(.*)"),library.post);
adminApp.get("/library/flows",library.getAll); adminApp.get("/library/flows",library.getAll);
adminApp.get(new RegExp("/library/flows\/(.*)"),library.get); adminApp.get(new RegExp("/library/flows\/(.*)"),library.get);
// Error Handler // Error Handler
adminApp.use(errorHandler); adminApp.use(errorHandler);
} }

View File

@ -34,6 +34,7 @@ module.exports = {
res.send(redNodes.getNodeConfigs()); res.send(redNodes.getNodeConfigs());
} }
}, },
post: function(req,res) { post: function(req,res) {
if (!settings.available()) { if (!settings.available()) {
res.send(400,new Error("Settings unavailable").toString()); res.send(400,new Error("Settings unavailable").toString());
@ -55,7 +56,7 @@ module.exports = {
return; return;
} }
promise.then(function(info) { promise.then(function(info) {
res.json(info); res.json(redNodes.getModuleInfo(node.module));
}).otherwise(function(err) { }).otherwise(function(err) {
if (err.code === 404) { if (err.code === 404) {
res.send(404); res.send(404);
@ -64,31 +65,25 @@ module.exports = {
} }
}); });
}, },
delete: function(req,res) { delete: function(req,res) {
if (!settings.available()) { if (!settings.available()) {
res.send(400,new Error("Settings unavailable").toString()); res.send(400,new Error("Settings unavailable").toString());
return; return;
} }
var id = req.params.id; var mod = req.params.mod;
var removedNodes = [];
try { try {
var node = redNodes.getNodeInfo(id);
var promise = null; var promise = null;
if (!node) { var module = redNodes.getNodeModuleInfo(mod);
var module = redNodes.getNodeModuleInfo(id); if (!module) {
if (!module) { res.send(404);
res.send(404); return;
return;
} else {
promise = server.uninstallModule(id);
}
} else { } else {
promise = when.resolve([redNodes.removeNode(id)]).then(server.reportRemovedModules); promise = server.uninstallModule(mod);
} }
promise.then(function(removedNodes) { promise.then(function() {
res.json(removedNodes); res.send(204);
}).otherwise(function(err) { }).otherwise(function(err) {
res.send(400,err.toString()); res.send(400,err.toString());
}); });
@ -96,11 +91,11 @@ module.exports = {
res.send(400,err.toString()); res.send(400,err.toString());
} }
}, },
get: function(req,res) { getSet: function(req,res) {
var id = req.params.id; var id = req.params.mod + "/" + req.params.set;
var result = null; var result = null;
if (req.get("accept") == "application/json") { if (req.get("accept") === "application/json") {
result = redNodes.getNodeInfo(id); result = redNodes.getNodeInfo(id);
} else { } else {
result = redNodes.getNodeConfig(id); result = redNodes.getNodeConfig(id);
@ -111,8 +106,18 @@ module.exports = {
res.send(404); res.send(404);
} }
}, },
put: function(req,res) { getModule: function(req,res) {
var module = req.params.mod;
var result = redNodes.getModuleInfo(module);
if (result) {
res.send(result);
} else {
res.send(404);
}
},
putSet: function(req,res) {
if (!settings.available()) { if (!settings.available()) {
res.send(400,new Error("Settings unavailable").toString()); res.send(400,new Error("Settings unavailable").toString());
return; return;
@ -123,9 +128,9 @@ module.exports = {
return; return;
} }
try { try {
var info; var id = req.params.mod+"/"+req.params.set;
var id = req.params.id;
var node = redNodes.getNodeInfo(id); var node = redNodes.getNodeInfo(id);
var info;
if (!node) { if (!node) {
res.send(404); res.send(404);
} else if (!node.err && node.enabled === body.enabled) { } else if (!node.err && node.enabled === body.enabled) {
@ -150,6 +155,51 @@ module.exports = {
} }
} catch(err) { } catch(err) {
res.send(400,err.toString()); res.send(400,err.toString());
} }
},
putModule: function(req,res) {
if (!settings.available()) {
res.send(400,new Error("Settings unavailable").toString());
return;
}
var body = req.body;
if (!body.hasOwnProperty("enabled")) {
res.send(400,"Invalid request");
return;
}
try {
var mod = req.params.mod;
var module = redNodes.getModuleInfo(mod);
if (!module) {
res.send(404);
return;
}
var nodes = module.nodes;
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
var info;
if (node.err || node.enabled !== body.enabled) {
if (body.enabled) {
info = redNodes.enableNode(node.id);
} else {
info = redNodes.disableNode(node.id);
}
if (info.enabled === body.enabled && !info.err) {
comms.publish("node/"+(body.enabled?"enabled":"disabled"),info,false);
util.log("[red] "+(body.enabled?"Enabled":"Disabled")+" node types:");
for (var j = 0; j < info.types.length; j++) {
util.log("[red] - " + info.types[j]);
}
} else if (body.enabled && info.err) {
util.log("[red] Failed to enable node:");
util.log("[red] - "+info.name+" : "+info.err);
}
}
}
res.json(redNodes.getModuleInfo(mod));
} catch(err) {
res.send(400,err.toString());
}
} }
} };

View File

@ -1,32 +0,0 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var redNodes = require("../nodes");
module.exports = {
getAll: function(req,res) {
res.json(redNodes.getPluginList());
},
get: function(req,res) {
var id = req.params.id;
var result = redNodes.getPluginInfo(id);
if (result) {
res.send(result);
} else {
res.send(404);
}
}
};

View File

@ -1,53 +0,0 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var path = require("path");
var fs = require("fs");
var userHome = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
var configDir = path.join(userHome,".nodered");
var configFile = path.join(configDir,"config.json");
var config;
function load() {
if (config == null) {
try {
config = JSON.parse(fs.readFileSync(configFile));
} catch(err) {
config = {};
}
}
}
function save() {
try {
fs.mkdirSync(configDir);
} catch(err) {
if (err.code != "EEXIST") {
throw err;
}
}
fs.writeFileSync(configFile,JSON.stringify(config,null,4));
}
module.exports = {
unload: function() {
config = null;
}
};
module.exports.__defineGetter__('target',function() { load(); return config.target|| "http://localhost:1880" });
module.exports.__defineSetter__('target',function(v) { load(); config.target = v; save();});

View File

@ -1,51 +0,0 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var when = require("when");
var request = require("request");
var config = require("./config");
module.exports = function(path, options) {
var basePath = config.target;
return when.promise(function(resolve,reject) {
options.headers = options.headers||{};
options.headers['Accept'] = 'application/json';
if (options.method == 'PUT' || options.method == "POST") {
options.headers['content-type'] = 'application/json';
}
options.url = basePath+path;
// Pull out the request function so we can stub it in the tests
var requestFunc = request.get;
if (options.method == 'PUT') {
requestFunc = request.put;
} else if (options.method == 'POST') {
requestFunc = request.post;
} else if (options.method == 'DELETE') {
requestFunc = request.del;
}
requestFunc(options, function(error,response,body) {
if (!error && response.statusCode == 200) {
resolve(JSON.parse(body));
} else if (error) {
reject(error.toString());
} else {
reject(response.statusCode+": "+body)
}
});
});
}

View File

@ -1,151 +0,0 @@
#!/usr/bin/env node
;(function() {
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var request = require("request");
var colors = require('colors');
var apiRequest = require("./lib/request");
var config = require("./lib/config");
var commands = {
"target": function() {
var target = process.argv[3];
if (target) {
if (!/^https?:\/\/.+/.test(target)) {
console.warn("Invalid target url");
return;
}
if (target.slice(-1) == "/") {
target = target.slice(0,target.length-1);
}
var oldTarget = config.target;
config.target = target;
} else {
console.log("Target: ".yellow+config.target);
}
},
"nodes": function() {
apiRequest('/nodes',{}).then(logNodeList).otherwise(logFailure);
},
"node": function() {
apiRequest('/nodes/'+process.argv[3],{}).then(logNodeList).otherwise(logFailure);
},
"enable-node": function() {
apiRequest('/nodes/'+process.argv[3],{
method: "PUT",
body: JSON.stringify({enabled:true})
}).then(logNodeList).otherwise(logFailure);
},
"disable-node": function() {
apiRequest('/nodes/'+process.argv[3],{
method: "PUT",
body: JSON.stringify({enabled:false})
}).then(logNodeList).otherwise(logFailure);
},
"install": function() {
apiRequest('/nodes',{
method: "POST",
body: JSON.stringify({module:process.argv[3]})
}).then(logNodeList).otherwise(logFailure);
},
"remove": function() {
apiRequest('/nodes/'+process.argv[3],{
method: "DELETE"
}).then(logNodeList).otherwise(logFailure);
},
"search": function() {
var options = {
method: "GET",
url: 'https://registry.npmjs.org/-/_view/byKeyword?startkey=["node-red"]&amp;endkey=["node-red",{}]&amp;group_level=3' ,
headers: {
'Accept': 'application/json',
}
};
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
var info = (JSON.parse(body)).rows;
var filter = null;
if (process.argv[3]) {
filter = new RegExp(process.argv[3]);
}
for (var i=0;i<info.length;i++) {
var n = info[i];
if (!filter || filter.test(n.key[1]) || filter.test(n.key[2])) {
console.log(n.key[1] + (" - "+ n.key[2]).grey);
}
}
} else if (error) {
console.log(error.toString().red);
} else {
console.log((response.statusCode+": "+body).red);
}
});
}
}
function logNodeList(nodes) {
if (!util.isArray(nodes)) {
nodes = [nodes];
}
for (var i=0;i<nodes.length;i++) {
var n = nodes[i];
console.log(formatNodeInfo(n))
}
}
function logFailure(msg) {
console.log(msg.red);
}
function formatBoolean(v,c) {
if (v) {
return ("["+c+"]");
} else {
return ("[ ]");
}
}
function formatNodeInfo(n) {
var inError = n.hasOwnProperty("err");
var str = formatBoolean(n.enabled,"X")+formatBoolean(n.loaded,"L")+" ";
str += n.id;
if (n.enabled && n.loaded) {
str = str.green;
} else if (n.enabled && n.err) {
str = str.red;
} else {
str = str.yellow;
}
if (n.module) {
str += " ["+n.module+"]";
}
str += " "+n.types.join(", ");
if (n.err) {
str+=" "+n.err.red;
}
return str;
}
if (commands[process.argv[2]]) {
commands[process.argv[2]].call();
}
})();

View File

@ -55,20 +55,21 @@ function checkTypeInUse(id) {
var nodeInfo = registry.getNodeInfo(id); var nodeInfo = registry.getNodeInfo(id);
if (!nodeInfo) { if (!nodeInfo) {
throw new Error("Unrecognised id: "+id); throw new Error("Unrecognised id: "+id);
} } else {
var inUse = {}; var inUse = {};
flows.each(function(n) { flows.each(function(n) {
inUse[n.type] = (inUse[n.type]||0)+1; inUse[n.type] = (inUse[n.type]||0)+1;
}); });
var nodesInUse = []; var nodesInUse = [];
nodeInfo.types.forEach(function(t) { nodeInfo.types.forEach(function(t) {
if (inUse[t]) { if (inUse[t]) {
nodesInUse.push(t); nodesInUse.push(t);
}
});
if (nodesInUse.length > 0) {
var msg = nodesInUse.join(", ");
throw new Error("Type in use: "+msg);
} }
});
if (nodesInUse.length > 0) {
var msg = nodesInUse.join(", ");
throw new Error("Type in use: "+msg);
} }
} }
@ -79,13 +80,16 @@ function removeNode(id) {
function removeModule(module) { function removeModule(module) {
var info = registry.getNodeModuleInfo(module); var info = registry.getNodeModuleInfo(module);
for (var i=0;i<info.nodes.length;i++) { if (!info) {
checkTypeInUse(info.nodes[i]); throw new Error("Unrecognised module: "+module);
} else {
for (var i=0;i<info.length;i++) {
checkTypeInUse(module+"/"+info[i]);
}
return registry.removeModule(module);
} }
return registry.removeModule(module);
} }
function disableNode(id) { function disableNode(id) {
checkTypeInUse(id); checkTypeInUse(id);
return registry.disableNode(id); return registry.disableNode(id);
@ -112,15 +116,20 @@ module.exports = {
// Node type registry // Node type registry
registerType: registerType, registerType: registerType,
getType: registry.get, getType: registry.get,
getNodeInfo: registry.getNodeInfo, getNodeInfo: registry.getNodeInfo,
getNodeModuleInfo: registry.getNodeModuleInfo,
getPluginInfo: registry.getPluginInfo,
getNodeList: registry.getNodeList, getNodeList: registry.getNodeList,
getPluginList: registry.getPluginList,
getNodeModuleInfo: registry.getNodeModuleInfo,
getModuleInfo: registry.getModuleInfo,
getModuleList: registry.getModuleList,
getNodeConfigs: registry.getNodeConfigs, getNodeConfigs: registry.getNodeConfigs,
getNodeConfig: registry.getNodeConfig, getNodeConfig: registry.getNodeConfig,
clearRegistry: registry.clear, clearRegistry: registry.clear,
cleanNodeList: registry.cleanNodeList, cleanModuleList: registry.cleanModuleList,
// Flow handling // Flow handling
loadFlows: flows.load, loadFlows: flows.load,

View File

@ -46,31 +46,48 @@ function filterNodeInfo(n) {
return r; return r;
} }
function getModule(id) {
return id.split("/")[0];
}
function getNode(id) {
return id.split("/")[1];
}
var registry = (function() { var registry = (function() {
var nodeConfigCache = null; var nodeConfigCache = null;
var nodeConfigs = {}; var moduleConfigs = {};
var nodeList = []; var nodeList = [];
var nodeConstructors = {}; var nodeConstructors = {};
var nodeTypeToId = {}; var nodeTypeToId = {};
var nodeModules = {}; var moduleNodes = {};
function saveNodeList() { function saveNodeList() {
var nodeList = {}; var moduleList = {};
for (var i in nodeConfigs) { for (var module in moduleConfigs) {
if (nodeConfigs.hasOwnProperty(i)) { if (moduleConfigs.hasOwnProperty(module)) {
var nodeConfig = nodeConfigs[i]; if (!moduleList[module]) {
var n = filterNodeInfo(nodeConfig); moduleList[module] = {};
n.file = nodeConfig.file; moduleList[module].name = module;
delete n.loaded; moduleList[module].nodes = {};
delete n.err; }
delete n.file; var nodes = moduleConfigs[module].nodes;
delete n.id; for(var node in nodes) {
nodeList[i] = n; if (nodes.hasOwnProperty(node)) {
var config = nodes[node];
var n = filterNodeInfo(config);
delete n.loaded;
delete n.err;
delete n.file;
delete n.id;
moduleList[module].nodes[node] = n;
}
}
} }
} }
if (settings.available()) { if (settings.available()) {
return settings.set("nodes",nodeList); return settings.set("modules",moduleList);
} else { } else {
return when.reject("Settings unavailable"); return when.reject("Settings unavailable");
} }
@ -79,45 +96,47 @@ var registry = (function() {
return { return {
init: function() { init: function() {
if (settings.available()) { if (settings.available()) {
nodeConfigs = settings.get("nodes")||{}; moduleConfigs = settings.get("modules")||{};
// Restore the node id property to individual entries
for (var id in nodeConfigs) {
if (nodeConfigs.hasOwnProperty(id)) {
nodeConfigs[id].id = id;
}
}
} else { } else {
nodeConfigs = {}; moduleConfigs = {};
} }
nodeModules = {}; moduleNodes = {};
nodeTypeToId = {}; nodeTypeToId = {};
nodeConstructors = {}; nodeConstructors = {};
nodeList = []; nodeList = [];
nodeConfigCache = null; nodeConfigCache = null;
}, },
addNodeSet: function(id,set,version) {
addNodeSet: function(id,set) {
if (!set.err) { if (!set.err) {
set.types.forEach(function(t) { set.types.forEach(function(t) {
nodeTypeToId[t] = id; nodeTypeToId[t] = id;
}); });
} }
if (set.module) { moduleNodes[set.module] = moduleNodes[set.module]||[];
nodeModules[set.module] = nodeModules[set.module]||{nodes:[]}; moduleNodes[set.module].push(set.name);
nodeModules[set.module].nodes.push(id);
if (!moduleConfigs[set.module]) {
moduleConfigs[set.module] = {
name: set.module,
nodes: {}
};
} }
nodeConfigs[id] = set; if (version) {
moduleConfigs[set.module].version = version;
}
moduleConfigs[set.module].nodes[set.name] = set;
nodeList.push(id); nodeList.push(id);
nodeConfigCache = null; nodeConfigCache = null;
}, },
removeNode: function(id) { removeNode: function(id) {
var config = nodeConfigs[id]; var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
if (!config) { if (!config) {
throw new Error("Unrecognised id: "+id); throw new Error("Unrecognised id: "+id);
} }
delete nodeConfigs[id]; delete moduleConfigs[getModule(id)].nodes[getNode(id)];
var i = nodeList.indexOf(id); var i = nodeList.indexOf(id);
if (i > -1) { if (i > -1) {
nodeList.splice(i,1); nodeList.splice(i,1);
@ -135,62 +154,81 @@ var registry = (function() {
if (!settings.available()) { if (!settings.available()) {
throw new Error("Settings unavailable"); throw new Error("Settings unavailable");
} }
var nodes = nodeModules[module]; var nodes = moduleNodes[module];
if (!nodes) { if (!nodes) {
throw new Error("Unrecognised module: "+module); throw new Error("Unrecognised module: "+module);
} }
var infoList = []; var infoList = [];
for (var i=0;i<nodes.nodes.length;i++) { for (var i=0;i<nodes.length;i++) {
infoList.push(registry.removeNode(nodes.nodes[i])); infoList.push(registry.removeNode(module+"/"+nodes[i]));
} }
delete nodeModules[module]; delete moduleNodes[module];
saveNodeList(); saveNodeList();
return infoList; return infoList;
}, },
getNodeInfo: function(typeOrId) { getNodeInfo: function(typeOrId) {
var id = typeOrId;
if (nodeTypeToId[typeOrId]) { if (nodeTypeToId[typeOrId]) {
return filterNodeInfo(nodeConfigs[nodeTypeToId[typeOrId]]); id = nodeTypeToId[typeOrId];
} else if (nodeConfigs[typeOrId]) { }
return filterNodeInfo(nodeConfigs[typeOrId]); if (id) {
var module = moduleConfigs[getModule(id)];
if (module) {
var config = module.nodes[getNode(id)];
if (config) {
return filterNodeInfo(config);
}
}
} }
return null; return null;
}, },
getNodeList: function() { getNodeList: function() {
var list = []; var list = [];
for (var id in nodeConfigs) { for (var module in moduleConfigs) {
if (nodeConfigs.hasOwnProperty(id)) { if (moduleConfigs.hasOwnProperty(module)) {
list.push(filterNodeInfo(nodeConfigs[id])); var nodes = moduleConfigs[module].nodes;
for (var node in nodes) {
if (nodes.hasOwnProperty(node)) {
list.push(filterNodeInfo(nodes[node]));
}
}
} }
} }
return list; return list;
}, },
getPluginList: function() { getModuleList: function() {
var list = []; var list = [];
for (var plugin in nodeModules) { for (var module in moduleNodes) {
if (nodeModules.hasOwnProperty(plugin)) { if (moduleNodes.hasOwnProperty(module)) {
var nodes = nodeModules[plugin].nodes; var nodes = moduleNodes[module];
var m = { var m = {
name: plugin, name: module,
version: moduleConfigs[module].version,
nodes: [] nodes: []
}; };
for (var i = 0; i < nodes.length; ++i) { for (var i = 0; i < nodes.length; ++i) {
m.nodes.push(filterNodeInfo(nodeConfigs[nodes[i]])); m.nodes.push(filterNodeInfo(moduleConfigs[module].nodes[nodes[i]]));
} }
list.push(m); list.push(m);
} }
} }
return list; return list;
}, },
getPluginInfo: function(plugin) { getModuleInfo: function(module) {
var nodes = nodeModules[plugin].nodes; if (moduleNodes[module]) {
var m = { var nodes = moduleNodes[module];
name: plugin, var m = {
nodes: [] name: module,
}; version: moduleConfigs[module].version,
for (var i = 0; i < nodes.length; ++i) { nodes: []
m.nodes.push(filterNodeInfo(nodeConfigs[nodes[i]])); };
for (var i = 0; i < nodes.length; ++i) {
m.nodes.push(filterNodeInfo(moduleConfigs[module].nodes[nodes[i]]));
}
return m;
} else {
return null;
} }
return m;
}, },
registerNodeConstructor: function(type,constructor) { registerNodeConstructor: function(type,constructor) {
if (nodeConstructors[type]) { if (nodeConstructors[type]) {
@ -214,7 +252,8 @@ var registry = (function() {
var result = ""; var result = "";
var script = ""; var script = "";
for (var i=0;i<nodeList.length;i++) { for (var i=0;i<nodeList.length;i++) {
var config = nodeConfigs[nodeList[i]]; var id = nodeList[i];
var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
if (config.enabled && !config.err) { if (config.enabled && !config.err) {
result += config.config; result += config.config;
script += config.script; script += config.script;
@ -231,7 +270,7 @@ var registry = (function() {
}, },
getNodeConfig: function(id) { getNodeConfig: function(id) {
var config = nodeConfigs[id]; var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
if (config) { if (config) {
var result = config.config; var result = config.config;
if (config.script) { if (config.script) {
@ -244,7 +283,15 @@ var registry = (function() {
}, },
getNodeConstructor: function(type) { getNodeConstructor: function(type) {
var config = nodeConfigs[nodeTypeToId[type]]; var id = nodeTypeToId[type];
var config;
if (typeof id === "undefined") {
config = undefined;
} else {
config = moduleConfigs[getModule(id)].nodes[getNode(id)];
}
if (!config || (config.enabled && !config.err)) { if (!config || (config.enabled && !config.err)) {
return nodeConstructors[type]; return nodeConstructors[type];
} }
@ -253,7 +300,7 @@ var registry = (function() {
clear: function() { clear: function() {
nodeConfigCache = null; nodeConfigCache = null;
nodeConfigs = {}; moduleConfigs = {};
nodeList = []; nodeList = [];
nodeConstructors = {}; nodeConstructors = {};
nodeTypeToId = {}; nodeTypeToId = {};
@ -263,21 +310,23 @@ var registry = (function() {
return nodeTypeToId[type]; return nodeTypeToId[type];
}, },
getModuleInfo: function(type) { getNodeModuleInfo: function(module) {
return nodeModules[type]; return moduleNodes[module];
}, },
enableNodeSet: function(id) { enableNodeSet: function(typeOrId) {
if (!settings.available()) { if (!settings.available()) {
throw new Error("Settings unavailable"); throw new Error("Settings unavailable");
} }
var config;
if (nodeTypeToId[id]) { var id = typeOrId;
config = nodeConfigs[nodeTypeToId[id]]; if (nodeTypeToId[typeOrId]) {
} else { id = nodeTypeToId[typeOrId];
config = nodeConfigs[id];
} }
if (config) {
var config;
try {
config = moduleConfigs[getModule(id)].nodes[getNode(id)];
delete config.err; delete config.err;
config.enabled = true; config.enabled = true;
if (!config.loaded) { if (!config.loaded) {
@ -286,28 +335,28 @@ var registry = (function() {
} }
nodeConfigCache = null; nodeConfigCache = null;
saveNodeList(); saveNodeList();
} else { } catch (err) {
throw new Error("Unrecognised id: "+id); throw new Error("Unrecognised id: "+typeOrId);
} }
return filterNodeInfo(config); return filterNodeInfo(config);
}, },
disableNodeSet: function(id) { disableNodeSet: function(typeOrId) {
if (!settings.available()) { if (!settings.available()) {
throw new Error("Settings unavailable"); throw new Error("Settings unavailable");
} }
var config; var id = typeOrId;
if (nodeTypeToId[id]) { if (nodeTypeToId[typeOrId]) {
config = nodeConfigs[nodeTypeToId[id]]; id = nodeTypeToId[typeOrId];
} else {
config = nodeConfigs[id];
} }
if (config) { var config;
try {
config = moduleConfigs[getModule(id)].nodes[getNode(id)];
// TODO: persist setting // TODO: persist setting
config.enabled = false; config.enabled = false;
nodeConfigCache = null; nodeConfigCache = null;
saveNodeList(); saveNodeList();
} else { } catch (err) {
throw new Error("Unrecognised id: "+id); throw new Error("Unrecognised id: "+id);
} }
return filterNodeInfo(config); return filterNodeInfo(config);
@ -315,13 +364,18 @@ var registry = (function() {
saveNodeList: saveNodeList, saveNodeList: saveNodeList,
cleanNodeList: function() { cleanModuleList: function() {
var removed = false; var removed = false;
for (var id in nodeConfigs) { for (var mod in moduleConfigs) {
if (nodeConfigs.hasOwnProperty(id)) { if (moduleConfigs.hasOwnProperty(mod)) {
if (nodeConfigs[id].module && !nodeModules[nodeConfigs[id].module]) { if (moduleConfigs[mod] && !moduleNodes[mod]) {
registry.removeNode(id); var nodes = moduleConfigs[mod].nodes;
removed = true; for (var node in nodes) {
if (nodes.hasOwnProperty(node)) {
registry.removeNode(mod+"/"+node);
removed = true;
}
}
} }
} }
} }
@ -401,7 +455,7 @@ function scanTreeForNodesModules(moduleName) {
var files = fs.readdirSync(pm); var files = fs.readdirSync(pm);
for (var i=0;i<files.length;i++) { for (var i=0;i<files.length;i++) {
var fn = files[i]; var fn = files[i];
if (!registry.getModuleInfo(fn)) { if (!registry.getNodeModuleInfo(fn)) {
if (!moduleName || fn == moduleName) { if (!moduleName || fn == moduleName) {
var pkgfn = path.join(pm,fn,"package.json"); var pkgfn = path.join(pm,fn,"package.json");
try { try {
@ -435,7 +489,7 @@ function scanTreeForNodesModules(moduleName) {
* @param moduleDir the root directory of the package * @param moduleDir the root directory of the package
* @param pkg the module's package.json object * @param pkg the module's package.json object
*/ */
function loadNodesFromModule(moduleDir,pkg) { function loadNodesFromModule(moduleDir,pkg,version) {
var nodes = pkg['node-red'].nodes||{}; var nodes = pkg['node-red'].nodes||{};
var results = []; var results = [];
var iconDirs = []; var iconDirs = [];
@ -443,7 +497,7 @@ function loadNodesFromModule(moduleDir,pkg) {
if (nodes.hasOwnProperty(n)) { if (nodes.hasOwnProperty(n)) {
var file = path.join(moduleDir,nodes[n]); var file = path.join(moduleDir,nodes[n]);
try { try {
results.push(loadNodeConfig(file,pkg.name,n)); results.push(loadNodeConfig(file,pkg.name,n,version));
} catch(err) { } catch(err) {
} }
var iconDir = path.join(moduleDir,path.dirname(nodes[n]),"icons"); var iconDir = path.join(moduleDir,path.dirname(nodes[n]),"icons");
@ -474,20 +528,8 @@ function loadNodesFromModule(moduleDir,pkg) {
* types: an array of node type names in this file * types: an array of node type names in this file
* } * }
*/ */
function loadNodeConfig(file,module,name) { function loadNodeConfig(file,module,name,version) {
var id = crypto.createHash('sha1').update(file).digest("hex"); var id = module + "/" + name;
if (module && name) {
var newid = crypto.createHash('sha1').update(module+":"+name).digest("hex");
var existingInfo = registry.getNodeInfo(id);
if (existingInfo) {
// For a brief period, id for modules were calculated incorrectly.
// To prevent false-duplicates, this removes the old id entry
registry.removeNode(id);
registry.saveNodeList();
}
id = newid;
}
var info = registry.getNodeInfo(id); var info = registry.getNodeInfo(id);
var isEnabled = true; var isEnabled = true;
@ -501,18 +543,14 @@ function loadNodeConfig(file,module,name) {
var node = { var node = {
id: id, id: id,
module: module,
name: name,
file: file, file: file,
template: file.replace(/\.js$/,".html"), template: file.replace(/\.js$/,".html"),
enabled: isEnabled, enabled: isEnabled,
loaded:false loaded:false
}; };
if (module) {
node.name = module+":"+name;
node.module = module;
} else {
node.name = path.basename(file);
}
try { try {
var content = fs.readFileSync(node.template,'utf8'); var content = fs.readFileSync(node.template,'utf8');
@ -544,7 +582,7 @@ function loadNodeConfig(file,module,name) {
node.err = err.toString(); node.err = err.toString();
} }
} }
registry.addNodeSet(id,node); registry.addNodeSet(id,node,version);
return node; return node;
} }
@ -576,7 +614,7 @@ function load(defaultNodesDir,disableNodePathScan) {
var nodes = []; var nodes = [];
nodeFiles.forEach(function(file) { nodeFiles.forEach(function(file) {
try { try {
nodes.push(loadNodeConfig(file)); nodes.push(loadNodeConfig(file,"node-red",path.basename(file).replace(/^\d+-/,"").replace(/\.js$/,"")));
} catch(err) { } catch(err) {
// //
} }
@ -682,19 +720,19 @@ function addNode(file) {
} }
var nodes = []; var nodes = [];
try { try {
nodes.push(loadNodeConfig(file)); nodes.push(loadNodeConfig(file,"node-red",path.basename(file).replace(/^\d+-/,"").replace(/\.js$/,"")));
} catch(err) { } catch(err) {
return when.reject(err); return when.reject(err);
} }
return loadNodeList(nodes); return loadNodeList(nodes);
} }
function addModule(module) { function addModule(module,version) {
if (!settings.available()) { if (!settings.available()) {
throw new Error("Settings unavailable"); throw new Error("Settings unavailable");
} }
var nodes = []; var nodes = [];
if (registry.getModuleInfo(module)) { if (registry.getNodeModuleInfo(module)) {
return when.reject(new Error("Module already loaded")); return when.reject(new Error("Module already loaded"));
} }
var moduleFiles = scanTreeForNodesModules(module); var moduleFiles = scanTreeForNodesModules(module);
@ -704,7 +742,7 @@ function addModule(module) {
return when.reject(err); return when.reject(err);
} }
moduleFiles.forEach(function(moduleFile) { moduleFiles.forEach(function(moduleFile) {
nodes = nodes.concat(loadNodesFromModule(moduleFile.dir,moduleFile.package)); nodes = nodes.concat(loadNodesFromModule(moduleFile.dir,moduleFile.package,version));
}); });
return loadNodeList(nodes); return loadNodeList(nodes);
} }
@ -714,14 +752,19 @@ module.exports = {
load:load, load:load,
clear: registry.clear, clear: registry.clear,
registerType: registry.registerNodeConstructor, registerType: registry.registerNodeConstructor,
get: registry.getNodeConstructor, get: registry.getNodeConstructor,
getNodeInfo: registry.getNodeInfo, getNodeInfo: registry.getNodeInfo,
getNodeModuleInfo: registry.getModuleInfo,
getPluginInfo: registry.getPluginInfo,
getNodeList: registry.getNodeList, getNodeList: registry.getNodeList,
getPluginList: registry.getPluginList,
getNodeModuleInfo: registry.getNodeModuleInfo,
getModuleInfo: registry.getModuleInfo,
getModuleList: registry.getModuleList,
getNodeConfigs: registry.getAllNodeConfigs, getNodeConfigs: registry.getAllNodeConfigs,
getNodeConfig: registry.getNodeConfig, getNodeConfig: registry.getNodeConfig,
addNode: addNode, addNode: addNode,
removeNode: registry.removeNode, removeNode: registry.removeNode,
enableNode: registry.enableNodeSet, enableNode: registry.enableNodeSet,
@ -729,5 +772,5 @@ module.exports = {
addModule: addModule, addModule: addModule,
removeModule: registry.removeModule, removeModule: registry.removeModule,
cleanNodeList: registry.cleanNodeList cleanModuleList: registry.cleanModuleList
}; };

View File

@ -33,10 +33,10 @@ function init(_server,_settings) {
settings = _settings; settings = _settings;
comms.init(_server,_settings); comms.init(_server,_settings);
nodeApp = express(); nodeApp = express();
app = express(); app = express();
if (settings.httpAdminRoot !== false) { if (settings.httpAdminRoot !== false) {
require("./api").init(app); require("./api").init(app);
} }
@ -44,7 +44,7 @@ function init(_server,_settings) {
function start() { function start() {
var defer = when.defer(); var defer = when.defer();
storage.init(settings).then(function() { storage.init(settings).then(function() {
settings.load(storage).then(function() { settings.load(storage).then(function() {
console.log("\nWelcome to Node-RED\n===================\n"); console.log("\nWelcome to Node-RED\n===================\n");
@ -91,11 +91,11 @@ function start() {
} }
if (!settings.autoInstallModules) { if (!settings.autoInstallModules) {
util.log("[red] Removing modules from config"); util.log("[red] Removing modules from config");
redNodes.cleanNodeList(); redNodes.cleanModuleList();
} }
} }
defer.resolve(); defer.resolve();
redNodes.loadFlows(); redNodes.loadFlows();
}).otherwise(function(err) { }).otherwise(function(err) {
console.log(err); console.log(err);
@ -105,7 +105,7 @@ function start() {
}).otherwise(function(err) { }).otherwise(function(err) {
defer.reject(err); defer.reject(err);
}); });
return defer.promise; return defer.promise;
} }
@ -138,7 +138,7 @@ function reportRemovedModules(removedNodes) {
return removedNodes; return removedNodes;
} }
function installModule(module) { function installModule(module) {
//TODO: ensure module is 'safe' //TODO: ensure module is 'safe'
return when.promise(function(resolve,reject) { return when.promise(function(resolve,reject) {
if (/[\s;]/.test(module)) { if (/[\s;]/.test(module)) {
@ -162,8 +162,11 @@ function installModule(module) {
reject(new Error("Install failed")); reject(new Error("Install failed"));
} }
} else { } else {
util.log("[red] Installed module: "+module); var grandchild = child_process.exec('npm view '+module+' version', function(err, stdin, stdout) {
resolve(redNodes.addModule(module).then(reportAddedModules)); var version = stdin.replace(/\s/g, "");
util.log("[red] Installed module: "+module+":"+version);
resolve(redNodes.addModule(module,version).then(reportAddedModules));
});
} }
}); });
}); });
@ -200,11 +203,11 @@ function stop() {
comms.stop(); comms.stop();
} }
module.exports = { module.exports = {
init: init, init: init,
start: start, start: start,
stop: stop, stop: stop,
reportAddedModules: reportAddedModules, reportAddedModules: reportAddedModules,
reportRemovedModules: reportRemovedModules, reportRemovedModules: reportRemovedModules,
installModule: installModule, installModule: installModule,

View File

@ -28,7 +28,7 @@ var settings = require("../../../red/settings");
var nodes = require("../../../red/api/nodes"); var nodes = require("../../../red/api/nodes");
describe("nodes api", function() { describe("nodes api", function() {
var app; var app;
before(function() { before(function() {
@ -36,11 +36,13 @@ describe("nodes api", function() {
app.use(express.json()); app.use(express.json());
app.get("/nodes",nodes.getAll); app.get("/nodes",nodes.getAll);
app.post("/nodes",nodes.post); app.post("/nodes",nodes.post);
app.get("/nodes/:id",nodes.get); app.get("/nodes/:mod",nodes.getModule);
app.put("/nodes/:id",nodes.put); app.get("/nodes/:mod/:set",nodes.getSet);
app.put("/nodes/:mod",nodes.putModule);
app.put("/nodes/:mod/:set",nodes.putSet);
app.delete("/nodes/:id",nodes.delete); app.delete("/nodes/:id",nodes.delete);
}); });
describe('get nodes', function() { describe('get nodes', function() {
it('returns node list', function(done) { it('returns node list', function(done) {
var getNodeList = sinon.stub(redNodes,'getNodeList', function() { var getNodeList = sinon.stub(redNodes,'getNodeList', function() {
@ -59,7 +61,7 @@ describe("nodes api", function() {
done(); done();
}); });
}); });
it('returns node configs', function(done) { it('returns node configs', function(done) {
var getNodeConfigs = sinon.stub(redNodes,'getNodeConfigs', function() { var getNodeConfigs = sinon.stub(redNodes,'getNodeConfigs', function() {
return "<script></script>"; return "<script></script>";
@ -77,13 +79,46 @@ describe("nodes api", function() {
done(); done();
}); });
}); });
it('returns an individual node info', function(done) { it('returns node module info', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo', function(id) { var getNodeInfo = sinon.stub(redNodes,'getModuleInfo', function(id) {
return {"123":{id:"123"}}[id]; return {"node-red":{name:"node-red"}}[id];
}); });
request(app) request(app)
.get('/nodes/123') .get('/nodes/node-red')
.expect(200)
.end(function(err,res) {
getNodeInfo.restore();
if (err) {
throw err;
}
res.body.should.have.property("name","node-red");
done();
});
});
it('returns 404 for unknown module', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getModuleInfo', function(id) {
return {"node-red":{name:"node-red"}}[id];
});
request(app)
.get('/nodes/node-blue')
.expect(404)
.end(function(err,res) {
getNodeInfo.restore();
if (err) {
throw err;
}
done();
});
});
it('returns individual node info', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo', function(id) {
return {"node-red/123":{id:"node-red/123"}}[id];
});
request(app)
.get('/nodes/node-red/123')
.set('Accept', 'application/json') .set('Accept', 'application/json')
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
@ -91,17 +126,17 @@ describe("nodes api", function() {
if (err) { if (err) {
throw err; throw err;
} }
res.body.should.have.property("id","123"); res.body.should.have.property("id","node-red/123");
done(); done();
}); });
}); });
it('returns an individual node configs', function(done) { it('returns individual node configs', function(done) {
var getNodeConfig = sinon.stub(redNodes,'getNodeConfig', function(id) { var getNodeConfig = sinon.stub(redNodes,'getNodeConfig', function(id) {
return {"123":"<script></script>"}[id]; return {"node-red/123":"<script></script>"}[id];
}); });
request(app) request(app)
.get('/nodes/123') .get('/nodes/node-red/123')
.set('Accept', 'text/html') .set('Accept', 'text/html')
.expect(200) .expect(200)
.expect("<script></script>") .expect("<script></script>")
@ -113,13 +148,13 @@ describe("nodes api", function() {
done(); done();
}); });
}); });
it('returns 404 for unknown node', function(done) { it('returns 404 for unknown node', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo', function(id) { var getNodeInfo = sinon.stub(redNodes,'getNodeInfo', function(id) {
return {"123":{id:"123"}}[id]; return {"node-red/123":{id:"node-red/123"}}[id];
}); });
request(app) request(app)
.get('/nodes/456') .get('/nodes/node-red/456')
.set('Accept', 'application/json') .set('Accept', 'application/json')
.expect(404) .expect(404)
.end(function(err,res) { .end(function(err,res) {
@ -131,9 +166,9 @@ describe("nodes api", function() {
}); });
}); });
}); });
describe('install', function() { describe('install', function() {
it('returns 400 if settings are unavailable', function(done) { it('returns 400 if settings are unavailable', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return false; return false;
@ -166,19 +201,27 @@ describe("nodes api", function() {
done(); done();
}); });
}); });
describe('by module', function() { describe('by module', function() {
it('installs the module and returns node info', function(done) { it('installs the module and returns module info', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return true; return true;
}); });
var getNodeModuleInfo = sinon.stub(redNodes,'getNodeModuleInfo',function(id) { var getNodeModuleInfo = sinon.stub(redNodes,'getNodeModuleInfo',function(id) {
return null; return null;
}); });
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(module) {
if (module === "foo") {
return {
name:"foo",
nodes:[{id:123}]
};
}
});
var installModule = sinon.stub(server,'installModule', function() { var installModule = sinon.stub(server,'installModule', function() {
return when.resolve({id:"123"}); return when.resolve({id:"123"});
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo'})
@ -186,15 +229,18 @@ describe("nodes api", function() {
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore(); settingsAvailable.restore();
getNodeModuleInfo.restore(); getNodeModuleInfo.restore();
getModuleInfo.restore();
installModule.restore(); installModule.restore();
if (err) { if (err) {
throw err; throw err;
} }
res.body.should.have.property("id","123"); res.body.should.have.property("name","foo");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("id","123");
done(); done();
}); });
}); });
it('fails the install if already installed', function(done) { it('fails the install if already installed', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return true; return true;
@ -205,7 +251,7 @@ describe("nodes api", function() {
var installModule = sinon.stub(server,'installModule', function() { var installModule = sinon.stub(server,'installModule', function() {
return when.resolve({id:"123"}); return when.resolve({id:"123"});
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo'})
@ -220,7 +266,7 @@ describe("nodes api", function() {
done(); done();
}); });
}); });
it('fails the install if module error', function(done) { it('fails the install if module error', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return true; return true;
@ -231,7 +277,7 @@ describe("nodes api", function() {
var installModule = sinon.stub(server,'installModule', function() { var installModule = sinon.stub(server,'installModule', function() {
return when.reject(new Error("test error")); return when.reject(new Error("test error"));
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo'})
@ -259,7 +305,7 @@ describe("nodes api", function() {
err.code = 404; err.code = 404;
return when.reject(err); return when.reject(err);
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo'})
@ -292,9 +338,9 @@ describe("nodes api", function() {
done(); done();
}); });
}); });
describe('by module', function() { describe('by module', function() {
it('uninstalls the module and returns node info', function(done) { it('uninstalls the module', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return true; return true;
}); });
@ -307,10 +353,10 @@ describe("nodes api", function() {
var uninstallModule = sinon.stub(server,'uninstallModule', function() { var uninstallModule = sinon.stub(server,'uninstallModule', function() {
return when.resolve({id:"123"}); return when.resolve({id:"123"});
}); });
request(app) request(app)
.del('/nodes/foo') .del('/nodes/foo')
.expect(200) .expect(204)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore(); settingsAvailable.restore();
getNodeInfo.restore(); getNodeInfo.restore();
@ -319,11 +365,10 @@ describe("nodes api", function() {
if (err) { if (err) {
throw err; throw err;
} }
res.body.should.have.property("id","123");
done(); done();
}); });
}); });
it('fails the uninstall if the module is not installed', function(done) { it('fails the uninstall if the module is not installed', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return true; return true;
@ -334,7 +379,7 @@ describe("nodes api", function() {
var getNodeModuleInfo = sinon.stub(redNodes,'getNodeModuleInfo',function(id) { var getNodeModuleInfo = sinon.stub(redNodes,'getNodeModuleInfo',function(id) {
return null; return null;
}); });
request(app) request(app)
.del('/nodes/foo') .del('/nodes/foo')
.expect(404) .expect(404)
@ -381,7 +426,7 @@ describe("nodes api", function() {
}); });
}); });
describe('enable/disable', function() { describe('enable/disable', function() {
it('returns 400 if settings are unavailable', function(done) { it('returns 400 if settings are unavailable', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
@ -398,8 +443,28 @@ describe("nodes api", function() {
done(); done();
}); });
}); });
it('returns 400 for invalid payload', function(done) { it('returns 400 for invalid node payload', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() {
return true;
});
request(app)
.put('/nodes/node-red/foo')
.send({})
.expect(400)
.end(function(err,res) {
settingsAvailable.restore();
if (err) {
throw err;
}
res.text.should.equal("Invalid request");
done();
});
});
it('returns 400 for invalid module payload', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return true; return true;
}); });
@ -418,6 +483,7 @@ describe("nodes api", function() {
done(); done();
}); });
}); });
it('returns 404 for unknown node', function(done) { it('returns 404 for unknown node', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return true; return true;
@ -427,7 +493,7 @@ describe("nodes api", function() {
}); });
request(app) request(app)
.put('/nodes/foo') .put('/nodes/node-red/foo')
.send({enabled:false}) .send({enabled:false})
.expect(404) .expect(404)
.end(function(err,res) { .end(function(err,res) {
@ -439,7 +505,29 @@ describe("nodes api", function() {
done(); done();
}); });
}); });
it('returns 404 for unknown module', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() {
return true;
});
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) {
return null;
});
request(app)
.put('/nodes/node-blue')
.send({enabled:false})
.expect(404)
.end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
if (err) {
throw err;
}
done();
});
});
it('enables disabled node', function(done) { it('enables disabled node', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return true; return true;
@ -452,7 +540,7 @@ describe("nodes api", function() {
}); });
request(app) request(app)
.put('/nodes/foo') .put('/nodes/node-red/foo')
.send({enabled:true}) .send({enabled:true})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
@ -464,10 +552,11 @@ describe("nodes api", function() {
} }
res.body.should.have.property("id","123"); res.body.should.have.property("id","123");
res.body.should.have.property("enabled",true); res.body.should.have.property("enabled",true);
done(); done();
}); });
}); });
it('disables enabled node', function(done) { it('disables enabled node', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return true; return true;
@ -480,7 +569,7 @@ describe("nodes api", function() {
}); });
request(app) request(app)
.put('/nodes/foo') .put('/nodes/node-red/foo')
.send({enabled:false}) .send({enabled:false})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
@ -492,10 +581,11 @@ describe("nodes api", function() {
} }
res.body.should.have.property("id","123"); res.body.should.have.property("id","123");
res.body.should.have.property("enabled",false); res.body.should.have.property("enabled",false);
done(); done();
}); });
}); });
describe('no-ops if already in the right state', function() { describe('no-ops if already in the right state', function() {
function run(state,done) { function run(state,done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
@ -507,13 +597,13 @@ describe("nodes api", function() {
var enableNode = sinon.stub(redNodes,'enableNode',function(id) { var enableNode = sinon.stub(redNodes,'enableNode',function(id) {
return {id:"123",enabled: true,types:['a']}; return {id:"123",enabled: true,types:['a']};
}); });
var disableNode = sinon.stub(redNodes,'disableNode',function(id) { var disableNode = sinon.stub(redNodes,'disableNode',function(id) {
return {id:"123",enabled: false,types:['a']}; return {id:"123",enabled: false,types:['a']};
}); });
request(app) request(app)
.put('/nodes/foo') .put('/nodes/node-red/foo')
.send({enabled:state}) .send({enabled:state})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
@ -530,7 +620,7 @@ describe("nodes api", function() {
disableNodeCalled.should.be.false; disableNodeCalled.should.be.false;
res.body.should.have.property("id","123"); res.body.should.have.property("id","123");
res.body.should.have.property("enabled",state); res.body.should.have.property("enabled",state);
done(); done();
}); });
} }
@ -541,6 +631,7 @@ describe("nodes api", function() {
run(false,done); run(false,done);
}); });
}); });
describe('does not no-op if err on node', function() { describe('does not no-op if err on node', function() {
function run(state,done) { function run(state,done) {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
@ -552,13 +643,13 @@ describe("nodes api", function() {
var enableNode = sinon.stub(redNodes,'enableNode',function(id) { var enableNode = sinon.stub(redNodes,'enableNode',function(id) {
return {id:"123",enabled: true,types:['a']}; return {id:"123",enabled: true,types:['a']};
}); });
var disableNode = sinon.stub(redNodes,'disableNode',function(id) { var disableNode = sinon.stub(redNodes,'disableNode',function(id) {
return {id:"123",enabled: false,types:['a']}; return {id:"123",enabled: false,types:['a']};
}); });
request(app) request(app)
.put('/nodes/foo') .put('/nodes/node-red/foo')
.send({enabled:state}) .send({enabled:state})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
@ -575,7 +666,187 @@ describe("nodes api", function() {
disableNodeCalled.should.be.equal(!state); disableNodeCalled.should.be.equal(!state);
res.body.should.have.property("id","123"); res.body.should.have.property("id","123");
res.body.should.have.property("enabled",state); res.body.should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
it('enables disabled module', function(done) {
var n1 = {id:"123",enabled:false,types:['a']};
var n2 = {id:"456",enabled:false,types:['b']};
var settingsAvailable = sinon.stub(settings,'available', function() {
return true;
});
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(name) {
return {name:"node-red", nodes:[n1, n2]};
});
var enableNode = sinon.stub(redNodes,'enableNode');
enableNode.onFirstCall().returns((function() {
n1.enabled = true;
return n1;
})());
enableNode.onSecondCall().returns((function() {
n2.enabled = true;
return n2;
})());
enableNode.returns(null);
request(app)
.put('/nodes/node-red')
.send({enabled:true})
.expect(200)
.end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
enableNode.restore();
if (err) {
throw err;
}
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",true);
res.body.nodes[1].should.have.property("enabled",true);
done();
});
});
it('disables enabled module', function(done) {
var n1 = {id:"123",enabled:true,types:['a']};
var n2 = {id:"456",enabled:true,types:['b']};
var settingsAvailable = sinon.stub(settings,'available', function() {
return true;
});
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(name) {
return {name:"node-red", nodes:[n1, n2]};
});
var disableNode = sinon.stub(redNodes,'disableNode');
disableNode.onFirstCall().returns((function() {
n1.enabled = false;
return n1;
})());
disableNode.onSecondCall().returns((function() {
n2.enabled = false;
return n2;
})());
disableNode.returns(null);
request(app)
.put('/nodes/node-red')
.send({enabled:false})
.expect(200)
.end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
disableNode.restore();
if (err) {
throw err;
}
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",false);
res.body.nodes[1].should.have.property("enabled",false);
done();
});
});
describe('no-ops if a node in module already in the right state', function() {
function run(state,done) {
var node = {id:"123",enabled:state,types:['a']};
var settingsAvailable = sinon.stub(settings,'available', function() {
return true;
});
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) {
return {name:"node-red", nodes:[node]};
});
var enableNode = sinon.stub(redNodes,'enableNode',function(id) {
node.enabled = true;
return node;
});
var disableNode = sinon.stub(redNodes,'disableNode',function(id) {
node.enabled = false;
return node;
});
request(app)
.put('/nodes/node-red')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
enableNode.restore();
disableNode.restore();
if (err) {
throw err;
}
enableNodeCalled.should.be.false;
disableNodeCalled.should.be.false;
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
describe('does not no-op if err on a node in module', function() {
function run(state,done) {
var node = {id:"123",enabled:state,types:['a'],err:"foo"};
var settingsAvailable = sinon.stub(settings,'available', function() {
return true;
});
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) {
return {name:"node-red", nodes:[node]};
});
var enableNode = sinon.stub(redNodes,'enableNode',function(id) {
node.enabled = true;
return node;
});
var disableNode = sinon.stub(redNodes,'disableNode',function(id) {
node.enabled = false;
return node;
});
request(app)
.put('/nodes/node-red')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
enableNode.restore();
disableNode.restore();
if (err) {
throw err;
}
enableNodeCalled.should.be.equal(state);
disableNodeCalled.should.be.equal(!state);
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",state);
done(); done();
}); });
} }
@ -587,6 +858,6 @@ describe("nodes api", function() {
}); });
}); });
}); });
}); });

View File

@ -1,77 +0,0 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var request = require('supertest');
var express = require('express');
var sinon = require('sinon');
var when = require('when');
var app = express();
var redNodes = require("../../../red/nodes");
var server = require("../../../red/server");
var settings = require("../../../red/settings");
var plugins = require("../../../red/api/plugins");
describe("plugins api", function() {
var app;
before(function() {
app = express();
app.use(express.json());
app.get("/plugins",plugins.getAll);
app.get("/plugins/:id",plugins.get);
});
describe('get plugins', function() {
it('returns plugins list', function(done) {
var getPluginList = sinon.stub(redNodes,'getPluginList', function() {
return [1,2,3];
});
request(app)
.get('/plugins')
.expect(200)
.end(function(err,res) {
getPluginList.restore();
if (err) {
throw err;
}
res.body.should.be.an.Array.and.have.lengthOf(3);
done();
});
});
it('returns an individual plugin info', function(done) {
var getPluginInfo = sinon.stub(redNodes,'getPluginInfo', function(id) {
return {"name":"123", "nodes":[1,2,3]};
});
request(app)
.get('/plugins/123')
.expect(200)
.end(function(err,res) {
getPluginInfo.restore();
if (err) {
throw err;
}
res.body.should.have.property("name","123");
res.body.should.have.property("nodes",[1,2,3]);
done();
});
});
});
});

View File

@ -1,53 +0,0 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var fs = require("fs");
var config = require("../../../../red/cli/lib/config");
describe("cli config", function() {
afterEach(function() {
config.unload();
});
it('loads preferences when target referenced', sinon.test(function() {
this.stub(fs,"readFileSync",function() {
return '{"target":"http://example.com:1880"}'
});
config.target.should.eql("http://example.com:1880");
}));
it('provide default value for target', sinon.test(function() {
this.stub(fs,"readFileSync",function() {
return '{}'
});
config.target.should.eql("http://localhost:1880");
}));
it('saves preferences when target set', sinon.test(function() {
this.stub(fs,"readFileSync",function() {
return '{"target":"http://another.example.com:1880"}'
});
this.stub(fs,"writeFileSync",function() {});
config.target.should.eql("http://another.example.com:1880");
config.target = "http://final.example.com:1880";
fs.readFileSync.calledOnce.should.be.true;
fs.writeFileSync.calledOnce.should.be.true;
}));
});

View File

@ -1,46 +0,0 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var fs = require("fs");
var request = require("request");
var apiRequest = require("../../../../red/cli/lib/request");
var config = require("../../../../red/cli/lib/config");
describe("cli request", function() {
var sandbox = sinon.sandbox.create();
before(function() {
sandbox.stub(fs,"readFileSync",function() {
return '{"target":"http://example.com:1880"}'
});
});
after(function() {
sandbox.restore();
});
it('returns the json response to a get', sinon.test(function(done) {
this.stub(request, 'get').yields(null, {statusCode:200}, JSON.stringify({a: "b"}));
apiRequest("/foo",{}).then(function(res) {
res.should.eql({a:"b"});
done();
}).otherwise(function(err) {
done(err);
});
}));
});

View File

@ -1,15 +0,0 @@
/**
* Copyright 2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

View File

@ -23,7 +23,7 @@ var sinon = require('sinon');
var index = require("../../../red/nodes/index"); var index = require("../../../red/nodes/index");
describe("red/nodes/index", function() { describe("red/nodes/index", function() {
afterEach(function() { afterEach(function() {
index.clearRegistry(); index.clearRegistry();
}); });
@ -44,7 +44,7 @@ describe("red/nodes/index", function() {
return when(true); return when(true);
} }
}; };
var settings = { var settings = {
available: function() { return false } available: function() { return false }
}; };
@ -56,13 +56,13 @@ describe("red/nodes/index", function() {
// do nothing // do nothing
}); });
} }
it('nodes are initialised with credentials',function(done) { it('nodes are initialised with credentials',function(done) {
index.init(settings, storage); index.init(settings, storage);
index.registerType('test', TestNode); index.registerType('test', TestNode);
index.loadFlows().then(function() { index.loadFlows().then(function() {
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
testnode.credentials.should.have.property('b',1); testnode.credentials.should.have.property('b',1);
testnode.credentials.should.have.property('c',2); testnode.credentials.should.have.property('c',2);
done(); done();
@ -71,8 +71,8 @@ describe("red/nodes/index", function() {
}); });
}); });
it('flows should be initialised',function(done) { it('flows should be initialised',function(done) {
index.init(settings, storage); index.init(settings, storage);
index.loadFlows().then(function() { index.loadFlows().then(function() {
should.deepEqual(testFlows, index.getFlows()); should.deepEqual(testFlows, index.getFlows());
@ -82,7 +82,7 @@ describe("red/nodes/index", function() {
}); });
}); });
describe("registerType should register credentials definition", function() { describe("registerType should register credentials definition", function() {
var http = require('http'); var http = require('http');
var express = require('express'); var express = require('express');
@ -91,7 +91,7 @@ describe("red/nodes/index", function() {
var credentials = require("../../../red/nodes/credentials"); var credentials = require("../../../red/nodes/credentials");
var localfilesystem = require("../../../red/storage/localfilesystem"); var localfilesystem = require("../../../red/storage/localfilesystem");
var RED = require("../../../red/red.js"); var RED = require("../../../red/red.js");
var userDir = path.join(__dirname,".testUserHome"); var userDir = path.join(__dirname,".testUserHome");
before(function(done) { before(function(done) {
fs.remove(userDir,function(err) { fs.remove(userDir,function(err) {
@ -109,7 +109,7 @@ describe("red/nodes/index", function() {
RED.init(http.createServer(function(req,res){app(req,res)}), RED.init(http.createServer(function(req,res){app(req,res)}),
{userDir: userDir}); {userDir: userDir});
server.start().then(function () { server.start().then(function () {
done(); done();
}); });
}); });
}); });
@ -121,24 +121,24 @@ describe("red/nodes/index", function() {
index.load.restore(); index.load.restore();
localfilesystem.getCredentials.restore(); localfilesystem.getCredentials.restore();
}); });
it(': definition defined',function(done) { it(': definition defined',function(done) {
index.registerType('test', TestNode, { index.registerType('test', TestNode, {
credentials: { credentials: {
foo: {type:"test"} foo: {type:"test"}
} }
}); });
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
credentials.getDefinition("test").should.have.property('foo'); credentials.getDefinition("test").should.have.property('foo');
done(); done();
}); });
}); });
describe('allows nodes to be added/remove/enabled/disabled from the registry', function() { describe('allows nodes to be added/removed/enabled/disabled from the registry', function() {
var registry = require("../../../red/nodes/registry"); var registry = require("../../../red/nodes/registry");
var randomNodeInfo = {id:"5678",types:["random"]}; var randomNodeInfo = {id:"5678",types:["random"]};
before(function() { before(function() {
sinon.stub(registry,"getNodeInfo",function(id) { sinon.stub(registry,"getNodeInfo",function(id) {
if (id == "test") { if (id == "test") {
@ -162,9 +162,9 @@ describe("red/nodes/index", function() {
registry.disableNode.restore(); registry.disableNode.restore();
}); });
it(': allows an unused node type to be removed',function(done) { it(': allows an unused node type to be removed',function(done) {
index.init(settings, storage); index.init(settings, storage);
index.registerType('test', TestNode); index.registerType('test', TestNode);
index.loadFlows().then(function() { index.loadFlows().then(function() {
var info = index.removeNode("5678"); var info = index.removeNode("5678");
registry.removeNode.calledOnce.should.be.true; registry.removeNode.calledOnce.should.be.true;
@ -175,10 +175,10 @@ describe("red/nodes/index", function() {
done(err); done(err);
}); });
}); });
it(': allows an unused node type to be disabled',function(done) { it(': allows an unused node type to be disabled',function(done) {
index.init(settings, storage); index.init(settings, storage);
index.registerType('test', TestNode); index.registerType('test', TestNode);
index.loadFlows().then(function() { index.loadFlows().then(function() {
var info = index.disableNode("5678"); var info = index.disableNode("5678");
registry.disableNode.calledOnce.should.be.true; registry.disableNode.calledOnce.should.be.true;
@ -190,66 +190,131 @@ describe("red/nodes/index", function() {
}); });
}); });
it(': prevents removing a node type that is in use',function(done) { it(': prevents removing a node type that is in use',function(done) {
index.init(settings, storage); index.init(settings, storage);
index.registerType('test', TestNode); index.registerType('test', TestNode);
index.loadFlows().then(function() { index.loadFlows().then(function() {
/*jshint immed: false */ /*jshint immed: false */
(function() { (function() {
index.removeNode("test"); index.removeNode("test");
}).should.throw(); }).should.throw();
done(); done();
}).otherwise(function(err) { }).otherwise(function(err) {
done(err); done(err);
}); });
}); });
it(': prevents disabling a node type that is in use',function(done) { it(': prevents disabling a node type that is in use',function(done) {
index.init(settings, storage); index.init(settings, storage);
index.registerType('test', TestNode); index.registerType('test', TestNode);
index.loadFlows().then(function() { index.loadFlows().then(function() {
/*jshint immed: false */ /*jshint immed: false */
(function() { (function() {
index.disabledNode("test"); index.disabledNode("test");
}).should.throw(); }).should.throw();
done(); done();
}).otherwise(function(err) { }).otherwise(function(err) {
done(err); done(err);
}); });
}); });
it(': prevents removing a node type that is unknown',function(done) { it(': prevents removing a node type that is unknown',function(done) {
index.init(settings, storage); index.init(settings, storage);
index.registerType('test', TestNode); index.registerType('test', TestNode);
index.loadFlows().then(function() { index.loadFlows().then(function() {
/*jshint immed: false */ /*jshint immed: false */
(function() { (function() {
index.removeNode("doesnotexist"); index.removeNode("doesnotexist");
}).should.throw(); }).should.throw();
done(); done();
}).otherwise(function(err) { }).otherwise(function(err) {
done(err); done(err);
}); });
}); });
it(': prevents disabling a node type that is unknown',function(done) { it(': prevents disabling a node type that is unknown',function(done) {
index.init(settings, storage); index.init(settings, storage);
index.registerType('test', TestNode); index.registerType('test', TestNode);
index.loadFlows().then(function() { index.loadFlows().then(function() {
/*jshint immed: false */ /*jshint immed: false */
(function() { (function() {
index.disableNode("doesnotexist"); index.disableNode("doesnotexist");
}).should.throw(); }).should.throw();
done();
}).otherwise(function(err) {
done(err);
});
});
});
describe('allows modules to be removed from the registry', function() {
var registry = require("../../../red/nodes/registry");
var randomNodeInfo = {id:"5678",types:["random"]};
var randomModuleInfo = {
name:"random",
nodes: [randomNodeInfo]
};
before(function() {
sinon.stub(registry,"getNodeInfo",function(id) {
if (id == "node-red/foo") {
return {id:"1234",types:["test"]};
} else if (id == "doesnotexist") {
return null;
} else {
return randomNodeInfo;
}
});
sinon.stub(registry,"getNodeModuleInfo",function(module) {
if (module == "node-red") {
return ["foo"];
} else if (module == "doesnotexist") {
return null;
} else {
return randomModuleInfo.nodes;
}
});
sinon.stub(registry,"removeModule",function(id) {
return randomModuleInfo;
});
});
after(function() {
registry.getNodeInfo.restore();
registry.getNodeModuleInfo.restore();
registry.removeModule.restore();
});
it(': prevents removing a module that is in use',function(done) {
index.init(settings, storage);
index.registerType('test', TestNode);
index.loadFlows().then(function() {
/*jshint immed: false */
(function() {
index.removeModule("node-red");
}).should.throw();
done();
}).otherwise(function(err) {
done(err);
});
});
it(': prevents removing a module that is unknown',function(done) {
index.init(settings, storage);
index.registerType('test', TestNode);
index.loadFlows().then(function() {
/*jshint immed: false */
(function() {
index.removeModule("doesnotexist");
}).should.throw();
done(); done();
}).otherwise(function(err) { }).otherwise(function(err) {
done(err); done(err);
}); });
}); });
}); });
}); });

View File

@ -33,10 +33,10 @@ describe('NodeRegistry', function() {
var resourcesDir = __dirname+ path.sep + "resources" + path.sep; var resourcesDir = __dirname+ path.sep + "resources" + path.sep;
function stubSettings(s,available) { function stubSettings(s,available) {
s.available = function() {return available;} s.available = function() {return available;};
s.set = function(s,v) { return when.resolve()}, s.set = function(s,v) { return when.resolve();};
s.get = function(s) { return null;} s.get = function(s) { return null;};
return s return s;
} }
var settings = stubSettings({},false); var settings = stubSettings({},false);
var settingsWithStorage = stubSettings({},true); var settingsWithStorage = stubSettings({},true);
@ -56,8 +56,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "TestNode1",true).then(function() { typeRegistry.load(resourcesDir + "TestNode1",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/TestNode1");
list[0].should.have.property("name","TestNode1.js"); list[0].should.have.property("name","TestNode1");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["test-node-1"]); list[0].should.have.property("types",["test-node-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
@ -78,8 +79,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "TestNode2",true).then(function() { typeRegistry.load(resourcesDir + "TestNode2",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/TestNode2");
list[0].should.have.property("name","TestNode2.js"); list[0].should.have.property("name","TestNode2");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["test-node-2"]); list[0].should.have.property("types",["test-node-2"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
@ -98,8 +100,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "TestNode3",true).then(function() { typeRegistry.load(resourcesDir + "TestNode3",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/TestNode3");
list[0].should.have.property("name","TestNode3.js"); list[0].should.have.property("name","TestNode3");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["test-node-3"]); list[0].should.have.property("types",["test-node-3"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.have.property("err","fail"); list[0].should.have.property("err","fail");
@ -119,8 +122,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "MultipleNodes1",true).then(function() { typeRegistry.load(resourcesDir + "MultipleNodes1",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/MultipleNodes1");
list[0].should.have.property("name","MultipleNodes1.js"); list[0].should.have.property("name","MultipleNodes1");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["test-node-multiple-1a","test-node-multiple-1b"]); list[0].should.have.property("types",["test-node-multiple-1a","test-node-multiple-1b"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
@ -142,8 +146,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() { typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/NestedNode");
list[0].should.have.property("name","NestedNode.js"); list[0].should.have.property("name","NestedNode");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["nested-node-1"]); list[0].should.have.property("types",["nested-node-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
@ -159,7 +164,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() { typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("name","NestedNode.js"); list[0].should.have.property("id","node-red/NestedNode");
list[0].should.have.property("name","NestedNode");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["nested-node-1"]); list[0].should.have.property("types",["nested-node-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
@ -189,26 +196,13 @@ describe('NodeRegistry', function() {
typeRegistry.load("wontexist",true).then(function() { typeRegistry.load("wontexist",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(2); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/TestNode1");
list[0].should.have.property("name","TestNode1.js"); list[0].should.have.property("name","TestNode1");
list[0].should.have.property("types",["test-node-1"]); list[0].should.have.property("types",["test-node-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
list[1].should.have.property("id");
list[1].id.should.not.equal(list[0].id);
list[1].should.have.property("name","TestNode1.js");
list[1].should.have.property("types",["test-node-1"]);
list[1].should.have.property("enabled",true);
list[1].should.have.property("err");
/already registered/.test(list[1].err).should.be.true;
var nodeConstructor = typeRegistry.get("test-node-1");
// Verify the duplicate node hasn't replaced the original one
nodeConstructor.name.should.be.equal("TestNode");
done(); done();
}).catch(function(e) { }).catch(function(e) {
done(e); done(e);
@ -295,26 +289,30 @@ describe('NodeRegistry', function() {
var settings = { var settings = {
nodesDir:[resourcesDir + "TestNode1",resourcesDir + "TestNode2",resourcesDir + "TestNode3"], nodesDir:[resourcesDir + "TestNode1",resourcesDir + "TestNode2",resourcesDir + "TestNode3"],
available: function() { return true; }, available: function() { return true; },
set: function(s,v) {return when.resolve();}, set: function(s,v) { return when.resolve(); },
get: function(s) { return null;} get: function(s) { return null; }
} };
var settingsSave = sinon.spy(settings,"set"); var settingsSave = sinon.spy(settings,"set");
typeRegistry.init(settings); typeRegistry.init(settings);
typeRegistry.load("wontexist",true).then(function() { typeRegistry.load("wontexist",true).then(function() {
var list = typeRegistry.getNodeList(); var nodeList = typeRegistry.getNodeList();
list.should.be.Array.and.have.length(3); var moduleList = typeRegistry.getModuleList();
nodeList.should.be.Array.and.have.length(3);
moduleList.should.be.Array.and.have.length(1);
settingsSave.callCount.should.equal(1); settingsSave.callCount.should.equal(1);
settingsSave.firstCall.args[0].should.be.equal("nodes"); settingsSave.firstCall.args[0].should.be.equal("modules");
var savedList = settingsSave.firstCall.args[1]; var savedList = settingsSave.firstCall.args[1];
savedList[list[0].id].name == list[0].name; savedList[moduleList[0].name].name.should.equal(moduleList[0].name);
savedList[list[1].id].name == list[1].name;
savedList[list[2].id].name == list[2].name;
savedList[list[0].id].should.not.have.property("err"); savedList[moduleList[0].name].nodes[moduleList[0].nodes[0].name].name.should.equal(moduleList[0].nodes[0].name);
savedList[list[1].id].should.not.have.property("err"); savedList[moduleList[0].name].nodes[moduleList[0].nodes[1].name].name.should.equal(moduleList[0].nodes[1].name);
savedList[list[2].id].should.not.have.property("err"); savedList[moduleList[0].name].nodes[moduleList[0].nodes[2].name].name.should.equal(moduleList[0].nodes[2].name);
savedList[moduleList[0].name].nodes[moduleList[0].nodes[0].name].should.not.have.property("err");
savedList[moduleList[0].name].nodes[moduleList[0].nodes[1].name].should.not.have.property("err");
savedList[moduleList[0].name].nodes[moduleList[0].nodes[2].name].should.not.have.property("err");
done(); done();
}).catch(function(e) { }).catch(function(e) {
@ -336,10 +334,12 @@ describe('NodeRegistry', function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.be.empty; list.should.be.an.Array.and.be.empty;
// TODO: Needs module and name params for loadNodeConfig
typeRegistry.addNode(resourcesDir + "TestNode1/TestNode1.js").then(function(node) { typeRegistry.addNode(resourcesDir + "TestNode1/TestNode1.js").then(function(node) {
list = typeRegistry.getNodeList(); list = typeRegistry.getNodeList();
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/TestNode1");
list[0].should.have.property("name","TestNode1.js"); list[0].should.have.property("name","TestNode1");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["test-node-1"]); list[0].should.have.property("types",["test-node-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
@ -386,8 +386,9 @@ describe('NodeRegistry', function() {
var id = list[0].id; var id = list[0].id;
var type = list[0].types[0]; var type = list[0].types[0];
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/TestNode1");
list[0].should.have.property("name","TestNode1.js"); list[0].should.have.property("name","TestNode1");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["test-node-1"]); list[0].should.have.property("types",["test-node-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
@ -405,7 +406,7 @@ describe('NodeRegistry', function() {
}); });
it('returns plugins list', function(done) { it('returns modules list', function(done) {
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
@ -438,7 +439,7 @@ describe('NodeRegistry', function() {
typeRegistry.load("wontexist",true).then(function(){ typeRegistry.load("wontexist",true).then(function(){
typeRegistry.addModule("TestNodeModule").then(function() { typeRegistry.addModule("TestNodeModule").then(function() {
var list = typeRegistry.getPluginList(); var list = typeRegistry.getModuleList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("name", "TestNodeModule"); list[0].should.have.property("name", "TestNodeModule");
list[0].should.have.property("nodes"); list[0].should.have.property("nodes");
@ -457,7 +458,7 @@ describe('NodeRegistry', function() {
}); });
}); });
it('returns plugin info', function(done) { it('returns module info', function(done) {
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
@ -490,11 +491,11 @@ describe('NodeRegistry', function() {
typeRegistry.load("wontexist",true).then(function(){ typeRegistry.load("wontexist",true).then(function(){
typeRegistry.addModule("TestNodeModule").then(function(nodes) { typeRegistry.addModule("TestNodeModule").then(function(nodes) {
var list = typeRegistry.getPluginList(); var list = typeRegistry.getModuleList();
var plugin = typeRegistry.getPluginInfo(list[0].name); var module = typeRegistry.getModuleInfo(list[0].name);
plugin.should.have.property("name", list[0].name); module.should.have.property("name", list[0].name);
plugin.should.have.property("nodes", nodes); module.should.have.property("nodes", nodes);
done(); done();
}).catch(function(e) { }).catch(function(e) {
done(e); done(e);
@ -532,8 +533,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "TestNode1",true).then(function() { typeRegistry.load(resourcesDir + "TestNode1",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/TestNode1");
list[0].should.have.property("name","TestNode1.js"); list[0].should.have.property("name","TestNode1");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["test-node-1"]); list[0].should.have.property("types",["test-node-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.have.property("loaded",true); list[0].should.have.property("loaded",true);
@ -552,7 +554,6 @@ describe('NodeRegistry', function() {
var nodeConstructor = typeRegistry.get("test-node-1"); var nodeConstructor = typeRegistry.get("test-node-1");
(typeof nodeConstructor).should.be.equal("undefined"); (typeof nodeConstructor).should.be.equal("undefined");
done(); done();
}).catch(function(e) { }).catch(function(e) {
done(e); done(e);
@ -612,14 +613,16 @@ describe('NodeRegistry', function() {
typeRegistry.load("wontexist",false).then(function(){ typeRegistry.load("wontexist",false).then(function(){
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(2); list.should.be.an.Array.and.have.lengthOf(2);
list[0].should.have.property("id"); list[0].should.have.property("id","TestNodeModule/TestNodeMod1");
list[0].should.have.property("name","TestNodeModule:TestNodeMod1"); list[0].should.have.property("name","TestNodeMod1");
list[0].should.have.property("module","TestNodeModule");
list[0].should.have.property("types",["test-node-mod-1"]); list[0].should.have.property("types",["test-node-mod-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
list[1].should.have.property("id"); list[1].should.have.property("id","TestNodeModule/TestNodeMod2");
list[1].should.have.property("name","TestNodeModule:TestNodeMod2"); list[1].should.have.property("name","TestNodeMod2");
list[1].should.have.property("module","TestNodeModule");
list[1].should.have.property("types",["test-node-mod-2"]); list[1].should.have.property("types",["test-node-mod-2"]);
list[1].should.have.property("enabled",true); list[1].should.have.property("enabled",true);
list[1].should.have.property("err"); list[1].should.have.property("err");
@ -681,14 +684,16 @@ describe('NodeRegistry', function() {
typeRegistry.addModule("TestNodeModule").then(function(node) { typeRegistry.addModule("TestNodeModule").then(function(node) {
list = typeRegistry.getNodeList(); list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(2); list.should.be.an.Array.and.have.lengthOf(2);
list[0].should.have.property("id"); list[0].should.have.property("id","TestNodeModule/TestNodeMod1");
list[0].should.have.property("name","TestNodeModule:TestNodeMod1"); list[0].should.have.property("name","TestNodeMod1");
list[0].should.have.property("module","TestNodeModule");
list[0].should.have.property("types",["test-node-mod-1"]); list[0].should.have.property("types",["test-node-mod-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
list[1].should.have.property("id"); list[1].should.have.property("id","TestNodeModule/TestNodeMod2");
list[1].should.have.property("name","TestNodeModule:TestNodeMod2"); list[1].should.have.property("name","TestNodeMod2");
list[1].should.have.property("module","TestNodeModule");
list[1].should.have.property("types",["test-node-mod-2"]); list[1].should.have.property("types",["test-node-mod-2"]);
list[1].should.have.property("enabled",true); list[1].should.have.property("enabled",true);
list[1].should.have.property("err"); list[1].should.have.property("err");
@ -708,6 +713,60 @@ describe('NodeRegistry', function() {
}); });
}); });
it('adds module with version number', function(done) {
var fs = require("fs");
var path = require("path");
var pathJoin = (function() {
var _join = path.join;
return sinon.stub(path,"join",function() {
if (arguments.length == 3 && arguments[2] == "package.json") {
return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1],arguments[2]);
}
if (arguments.length == 2 && arguments[1] == "TestNodeModule") {
return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1]);
}
return _join.apply(this,arguments);
});
})();
var readdirSync = (function() {
var originalReaddirSync = fs.readdirSync;
var callCount = 0;
return sinon.stub(fs,"readdirSync",function(dir) {
var result = [];
if (callCount == 1) {
result = originalReaddirSync(resourcesDir + "TestNodeModule" + path.sep + "node_modules");
}
callCount++;
return result;
});
})();
typeRegistry.init(settingsWithStorage);
typeRegistry.load("wontexist",true).then(function(){
typeRegistry.addModule("TestNodeModule","0.0.1").then(function(node) {
var module = typeRegistry.getModuleInfo("TestNodeModule");
module.should.have.property("name","TestNodeModule");
module.should.have.property("version","0.0.1");
var modules = typeRegistry.getModuleList();
modules[0].should.have.property("name","TestNodeModule");
modules[0].should.have.property("version","0.0.1");
done();
}).catch(function(e) {
done(e);
});
}).catch(function(e) {
done(e);
}).finally(function() {
readdirSync.restore();
pathJoin.restore();
});
});
it('rejects adding duplicate node modules', function(done) { it('rejects adding duplicate node modules', function(done) {
var fs = require("fs"); var fs = require("fs");
@ -847,13 +906,14 @@ describe('NodeRegistry', function() {
}); });
it('allows nodes to be enabled and disabled by hex-id', function(done) { it('allows nodes to be enabled and disabled by id', function(done) {
typeRegistry.init(settingsWithStorage); typeRegistry.init(settingsWithStorage);
typeRegistry.load(resourcesDir+path.sep+"TestNode1",true).then(function() { typeRegistry.load(resourcesDir+path.sep+"TestNode1",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/TestNode1");
list[0].should.have.property("name","TestNode1.js"); list[0].should.have.property("name","TestNode1");
list[0].should.have.property("module","node-red");
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
var nodeConfig = typeRegistry.getNodeConfigs(); var nodeConfig = typeRegistry.getNodeConfigs();
@ -892,8 +952,9 @@ describe('NodeRegistry', function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1); list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id"); list[0].should.have.property("id","node-red/TestNode1");
list[0].should.have.property("name","TestNode1.js"); list[0].should.have.property("name","TestNode1");
list[0].should.have.property("module","node-red");
list[0].should.have.property("types",["test-node-1"]); list[0].should.have.property("types",["test-node-1"]);
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
@ -930,14 +991,14 @@ describe('NodeRegistry', function() {
}); });
it('fails to enable/disable non-existent nodes', function(done) { it('fails to enable/disable non-existent nodes', function(done) {
typeRegistry.init(settings); typeRegistry.init(settingsWithStorage);
typeRegistry.load("wontexist",true).then(function() { typeRegistry.load("wontexist",true).then(function() {
var list = typeRegistry.getNodeList(); var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.be.empty; list.should.be.an.Array.and.be.empty;
/*jshint immed: false */ /*jshint immed: false */
(function() { (function() {
typeRegistry.disableNode("123"); typeRegistry.disableNode("123");
}).should.throw(); }).should.throw();
/*jshint immed: false */ /*jshint immed: false */