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

View File

@ -34,6 +34,7 @@ module.exports = {
res.send(redNodes.getNodeConfigs());
}
},
post: function(req,res) {
if (!settings.available()) {
res.send(400,new Error("Settings unavailable").toString());
@ -55,7 +56,7 @@ module.exports = {
return;
}
promise.then(function(info) {
res.json(info);
res.json(redNodes.getModuleInfo(node.module));
}).otherwise(function(err) {
if (err.code === 404) {
res.send(404);
@ -70,25 +71,19 @@ module.exports = {
res.send(400,new Error("Settings unavailable").toString());
return;
}
var id = req.params.id;
var removedNodes = [];
var mod = req.params.mod;
try {
var node = redNodes.getNodeInfo(id);
var promise = null;
if (!node) {
var module = redNodes.getNodeModuleInfo(id);
if (!module) {
res.send(404);
return;
} else {
promise = server.uninstallModule(id);
}
var module = redNodes.getNodeModuleInfo(mod);
if (!module) {
res.send(404);
return;
} else {
promise = when.resolve([redNodes.removeNode(id)]).then(server.reportRemovedModules);
promise = server.uninstallModule(mod);
}
promise.then(function(removedNodes) {
res.json(removedNodes);
promise.then(function() {
res.send(204);
}).otherwise(function(err) {
res.send(400,err.toString());
});
@ -97,10 +92,10 @@ module.exports = {
}
},
get: function(req,res) {
var id = req.params.id;
getSet: function(req,res) {
var id = req.params.mod + "/" + req.params.set;
var result = null;
if (req.get("accept") == "application/json") {
if (req.get("accept") === "application/json") {
result = redNodes.getNodeInfo(id);
} else {
result = redNodes.getNodeConfig(id);
@ -112,7 +107,17 @@ module.exports = {
}
},
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()) {
res.send(400,new Error("Settings unavailable").toString());
return;
@ -123,9 +128,9 @@ module.exports = {
return;
}
try {
var info;
var id = req.params.id;
var id = req.params.mod+"/"+req.params.set;
var node = redNodes.getNodeInfo(id);
var info;
if (!node) {
res.send(404);
} else if (!node.err && node.enabled === body.enabled) {
@ -151,5 +156,50 @@ module.exports = {
} catch(err) {
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);
if (!nodeInfo) {
throw new Error("Unrecognised id: "+id);
}
var inUse = {};
flows.each(function(n) {
inUse[n.type] = (inUse[n.type]||0)+1;
});
var nodesInUse = [];
nodeInfo.types.forEach(function(t) {
if (inUse[t]) {
nodesInUse.push(t);
} else {
var inUse = {};
flows.each(function(n) {
inUse[n.type] = (inUse[n.type]||0)+1;
});
var nodesInUse = [];
nodeInfo.types.forEach(function(t) {
if (inUse[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) {
var info = registry.getNodeModuleInfo(module);
for (var i=0;i<info.nodes.length;i++) {
checkTypeInUse(info.nodes[i]);
if (!info) {
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) {
checkTypeInUse(id);
return registry.disableNode(id);
@ -112,15 +116,20 @@ module.exports = {
// Node type registry
registerType: registerType,
getType: registry.get,
getNodeInfo: registry.getNodeInfo,
getNodeModuleInfo: registry.getNodeModuleInfo,
getPluginInfo: registry.getPluginInfo,
getNodeList: registry.getNodeList,
getPluginList: registry.getPluginList,
getNodeModuleInfo: registry.getNodeModuleInfo,
getModuleInfo: registry.getModuleInfo,
getModuleList: registry.getModuleList,
getNodeConfigs: registry.getNodeConfigs,
getNodeConfig: registry.getNodeConfig,
clearRegistry: registry.clear,
cleanNodeList: registry.cleanNodeList,
cleanModuleList: registry.cleanModuleList,
// Flow handling
loadFlows: flows.load,

View File

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

View File

@ -91,7 +91,7 @@ function start() {
}
if (!settings.autoInstallModules) {
util.log("[red] Removing modules from config");
redNodes.cleanNodeList();
redNodes.cleanModuleList();
}
}
defer.resolve();
@ -162,8 +162,11 @@ function installModule(module) {
reject(new Error("Install failed"));
}
} else {
util.log("[red] Installed module: "+module);
resolve(redNodes.addModule(module).then(reportAddedModules));
var grandchild = child_process.exec('npm view '+module+' version', function(err, stdin, stdout) {
var version = stdin.replace(/\s/g, "");
util.log("[red] Installed module: "+module+":"+version);
resolve(redNodes.addModule(module,version).then(reportAddedModules));
});
}
});
});

View File

@ -36,8 +36,10 @@ describe("nodes api", function() {
app.use(express.json());
app.get("/nodes",nodes.getAll);
app.post("/nodes",nodes.post);
app.get("/nodes/:id",nodes.get);
app.put("/nodes/:id",nodes.put);
app.get("/nodes/:mod",nodes.getModule);
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);
});
@ -78,12 +80,45 @@ describe("nodes api", function() {
});
});
it('returns an individual node info', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo', function(id) {
return {"123":{id:"123"}}[id];
it('returns node module info', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getModuleInfo', function(id) {
return {"node-red":{name:"node-red"}}[id];
});
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')
.expect(200)
.end(function(err,res) {
@ -91,17 +126,17 @@ describe("nodes api", function() {
if (err) {
throw err;
}
res.body.should.have.property("id","123");
res.body.should.have.property("id","node-red/123");
done();
});
});
it('returns an individual node configs', function(done) {
it('returns individual node configs', function(done) {
var getNodeConfig = sinon.stub(redNodes,'getNodeConfig', function(id) {
return {"123":"<script></script>"}[id];
return {"node-red/123":"<script></script>"}[id];
});
request(app)
.get('/nodes/123')
.get('/nodes/node-red/123')
.set('Accept', 'text/html')
.expect(200)
.expect("<script></script>")
@ -116,10 +151,10 @@ describe("nodes api", function() {
it('returns 404 for unknown node', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo', function(id) {
return {"123":{id:"123"}}[id];
return {"node-red/123":{id:"node-red/123"}}[id];
});
request(app)
.get('/nodes/456')
.get('/nodes/node-red/456')
.set('Accept', 'application/json')
.expect(404)
.end(function(err,res) {
@ -168,13 +203,21 @@ describe("nodes api", 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() {
return true;
});
var getNodeModuleInfo = sinon.stub(redNodes,'getNodeModuleInfo',function(id) {
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() {
return when.resolve({id:"123"});
});
@ -186,11 +229,14 @@ describe("nodes api", function() {
.end(function(err,res) {
settingsAvailable.restore();
getNodeModuleInfo.restore();
getModuleInfo.restore();
installModule.restore();
if (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();
});
});
@ -294,7 +340,7 @@ describe("nodes api", 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() {
return true;
});
@ -310,7 +356,7 @@ describe("nodes api", function() {
request(app)
.del('/nodes/foo')
.expect(200)
.expect(204)
.end(function(err,res) {
settingsAvailable.restore();
getNodeInfo.restore();
@ -319,7 +365,6 @@ describe("nodes api", function() {
if (err) {
throw err;
}
res.body.should.have.property("id","123");
done();
});
});
@ -399,7 +444,27 @@ describe("nodes api", function() {
});
});
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() {
return true;
});
@ -418,6 +483,7 @@ describe("nodes api", function() {
done();
});
});
it('returns 404 for unknown node', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() {
return true;
@ -427,7 +493,7 @@ describe("nodes api", function() {
});
request(app)
.put('/nodes/foo')
.put('/nodes/node-red/foo')
.send({enabled:false})
.expect(404)
.end(function(err,res) {
@ -440,6 +506,28 @@ describe("nodes api", function() {
});
});
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) {
var settingsAvailable = sinon.stub(settings,'available', function() {
return true;
@ -452,7 +540,7 @@ describe("nodes api", function() {
});
request(app)
.put('/nodes/foo')
.put('/nodes/node-red/foo')
.send({enabled:true})
.expect(200)
.end(function(err,res) {
@ -468,6 +556,7 @@ describe("nodes api", function() {
done();
});
});
it('disables enabled node', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() {
return true;
@ -480,7 +569,7 @@ describe("nodes api", function() {
});
request(app)
.put('/nodes/foo')
.put('/nodes/node-red/foo')
.send({enabled:false})
.expect(200)
.end(function(err,res) {
@ -496,6 +585,7 @@ describe("nodes api", function() {
done();
});
});
describe('no-ops if already in the right state', function() {
function run(state,done) {
var settingsAvailable = sinon.stub(settings,'available', function() {
@ -513,7 +603,7 @@ describe("nodes api", function() {
});
request(app)
.put('/nodes/foo')
.put('/nodes/node-red/foo')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
@ -541,6 +631,7 @@ describe("nodes api", function() {
run(false,done);
});
});
describe('does not no-op if err on node', function() {
function run(state,done) {
var settingsAvailable = sinon.stub(settings,'available', function() {
@ -558,7 +649,7 @@ describe("nodes api", function() {
});
request(app)
.put('/nodes/foo')
.put('/nodes/node-red/foo')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
@ -586,6 +677,186 @@ describe("nodes api", function() {
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();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
});

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

@ -135,7 +135,7 @@ describe("red/nodes/index", function() {
});
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 randomNodeInfo = {id:"5678",types:["random"]};
@ -248,8 +248,73 @@ describe("red/nodes/index", function() {
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();
}).otherwise(function(err) {
done(err);
});
});
});
});

View File

@ -33,10 +33,10 @@ describe('NodeRegistry', function() {
var resourcesDir = __dirname+ path.sep + "resources" + path.sep;
function stubSettings(s,available) {
s.available = function() {return available;}
s.set = function(s,v) { return when.resolve()},
s.get = function(s) { return null;}
return s
s.available = function() {return available;};
s.set = function(s,v) { return when.resolve();};
s.get = function(s) { return null;};
return s;
}
var settings = stubSettings({},false);
var settingsWithStorage = stubSettings({},true);
@ -56,8 +56,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "TestNode1",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id");
list[0].should.have.property("name","TestNode1.js");
list[0].should.have.property("id","node-red/TestNode1");
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("enabled",true);
list[0].should.not.have.property("err");
@ -78,8 +79,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "TestNode2",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id");
list[0].should.have.property("name","TestNode2.js");
list[0].should.have.property("id","node-red/TestNode2");
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("enabled",true);
list[0].should.not.have.property("err");
@ -98,8 +100,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "TestNode3",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id");
list[0].should.have.property("name","TestNode3.js");
list[0].should.have.property("id","node-red/TestNode3");
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("enabled",true);
list[0].should.have.property("err","fail");
@ -119,8 +122,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "MultipleNodes1",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id");
list[0].should.have.property("name","MultipleNodes1.js");
list[0].should.have.property("id","node-red/MultipleNodes1");
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("enabled",true);
list[0].should.not.have.property("err");
@ -142,8 +146,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id");
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("enabled",true);
list[0].should.not.have.property("err");
@ -159,7 +164,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() {
var list = typeRegistry.getNodeList();
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("enabled",true);
list[0].should.not.have.property("err");
@ -189,26 +196,13 @@ describe('NodeRegistry', function() {
typeRegistry.load("wontexist",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(2);
list[0].should.have.property("id");
list[0].should.have.property("name","TestNode1.js");
list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id","node-red/TestNode1");
list[0].should.have.property("name","TestNode1");
list[0].should.have.property("types",["test-node-1"]);
list[0].should.have.property("enabled",true);
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();
}).catch(function(e) {
done(e);
@ -295,26 +289,30 @@ describe('NodeRegistry', function() {
var settings = {
nodesDir:[resourcesDir + "TestNode1",resourcesDir + "TestNode2",resourcesDir + "TestNode3"],
available: function() { return true; },
set: function(s,v) {return when.resolve();},
get: function(s) { return null;}
}
set: function(s,v) { return when.resolve(); },
get: function(s) { return null; }
};
var settingsSave = sinon.spy(settings,"set");
typeRegistry.init(settings);
typeRegistry.load("wontexist",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.Array.and.have.length(3);
var nodeList = typeRegistry.getNodeList();
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.firstCall.args[0].should.be.equal("nodes");
settingsSave.firstCall.args[0].should.be.equal("modules");
var savedList = settingsSave.firstCall.args[1];
savedList[list[0].id].name == list[0].name;
savedList[list[1].id].name == list[1].name;
savedList[list[2].id].name == list[2].name;
savedList[moduleList[0].name].name.should.equal(moduleList[0].name);
savedList[list[0].id].should.not.have.property("err");
savedList[list[1].id].should.not.have.property("err");
savedList[list[2].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[moduleList[0].name].nodes[moduleList[0].nodes[1].name].name.should.equal(moduleList[0].nodes[1].name);
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();
}).catch(function(e) {
@ -336,10 +334,12 @@ describe('NodeRegistry', function() {
var list = typeRegistry.getNodeList();
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) {
list = typeRegistry.getNodeList();
list[0].should.have.property("id");
list[0].should.have.property("name","TestNode1.js");
list[0].should.have.property("id","node-red/TestNode1");
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("enabled",true);
list[0].should.not.have.property("err");
@ -386,8 +386,9 @@ describe('NodeRegistry', function() {
var id = list[0].id;
var type = list[0].types[0];
list[0].should.have.property("id");
list[0].should.have.property("name","TestNode1.js");
list[0].should.have.property("id","node-red/TestNode1");
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("enabled",true);
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 path = require("path");
@ -438,7 +439,7 @@ describe('NodeRegistry', function() {
typeRegistry.load("wontexist",true).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[0].should.have.property("name", "TestNodeModule");
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 path = require("path");
@ -490,11 +491,11 @@ describe('NodeRegistry', function() {
typeRegistry.load("wontexist",true).then(function(){
typeRegistry.addModule("TestNodeModule").then(function(nodes) {
var list = typeRegistry.getPluginList();
var list = typeRegistry.getModuleList();
var plugin = typeRegistry.getPluginInfo(list[0].name);
plugin.should.have.property("name", list[0].name);
plugin.should.have.property("nodes", nodes);
var module = typeRegistry.getModuleInfo(list[0].name);
module.should.have.property("name", list[0].name);
module.should.have.property("nodes", nodes);
done();
}).catch(function(e) {
done(e);
@ -532,8 +533,9 @@ describe('NodeRegistry', function() {
typeRegistry.load(resourcesDir + "TestNode1",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id");
list[0].should.have.property("name","TestNode1.js");
list[0].should.have.property("id","node-red/TestNode1");
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("enabled",true);
list[0].should.have.property("loaded",true);
@ -552,7 +554,6 @@ describe('NodeRegistry', function() {
var nodeConstructor = typeRegistry.get("test-node-1");
(typeof nodeConstructor).should.be.equal("undefined");
done();
}).catch(function(e) {
done(e);
@ -612,14 +613,16 @@ describe('NodeRegistry', function() {
typeRegistry.load("wontexist",false).then(function(){
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(2);
list[0].should.have.property("id");
list[0].should.have.property("name","TestNodeModule:TestNodeMod1");
list[0].should.have.property("id","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("enabled",true);
list[0].should.not.have.property("err");
list[1].should.have.property("id");
list[1].should.have.property("name","TestNodeModule:TestNodeMod2");
list[1].should.have.property("id","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("enabled",true);
list[1].should.have.property("err");
@ -681,14 +684,16 @@ describe('NodeRegistry', function() {
typeRegistry.addModule("TestNodeModule").then(function(node) {
list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(2);
list[0].should.have.property("id");
list[0].should.have.property("name","TestNodeModule:TestNodeMod1");
list[0].should.have.property("id","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("enabled",true);
list[0].should.not.have.property("err");
list[1].should.have.property("id");
list[1].should.have.property("name","TestNodeModule:TestNodeMod2");
list[1].should.have.property("id","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("enabled",true);
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) {
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.load(resourcesDir+path.sep+"TestNode1",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id");
list[0].should.have.property("name","TestNode1.js");
list[0].should.have.property("id","node-red/TestNode1");
list[0].should.have.property("name","TestNode1");
list[0].should.have.property("module","node-red");
list[0].should.have.property("enabled",true);
var nodeConfig = typeRegistry.getNodeConfigs();
@ -892,8 +952,9 @@ describe('NodeRegistry', function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.have.lengthOf(1);
list[0].should.have.property("id");
list[0].should.have.property("name","TestNode1.js");
list[0].should.have.property("id","node-red/TestNode1");
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("enabled",true);
@ -930,14 +991,14 @@ describe('NodeRegistry', function() {
});
it('fails to enable/disable non-existent nodes', function(done) {
typeRegistry.init(settings);
typeRegistry.init(settingsWithStorage);
typeRegistry.load("wontexist",true).then(function() {
var list = typeRegistry.getNodeList();
list.should.be.an.Array.and.be.empty;
/*jshint immed: false */
(function() {
typeRegistry.disableNode("123");
typeRegistry.disableNode("123");
}).should.throw();
/*jshint immed: false */