WIP: separate runtime and api components

This commit is contained in:
Nick O'Leary 2015-11-11 22:11:02 +00:00
parent 923a46d304
commit f43738446e
49 changed files with 695 additions and 612 deletions

17
red.js
View File

@ -25,7 +25,6 @@ var nopt = require("nopt");
var path = require("path"); var path = require("path");
var fs = require("fs-extra"); var fs = require("fs-extra");
var RED = require("./red/red.js"); var RED = require("./red/red.js");
var log = require("./red/log");
var server; var server;
var app = express(); var app = express();
@ -205,7 +204,7 @@ function basicAuthMiddleware(user,pass) {
} }
if (settings.httpAdminRoot !== false && settings.httpAdminAuth) { if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
RED.log.warn(log._("server.httpadminauth-deprecated")); RED.log.warn(RED.log._("server.httpadminauth-deprecated"));
app.use(settings.httpAdminRoot, basicAuthMiddleware(settings.httpAdminAuth.user,settings.httpAdminAuth.pass)); app.use(settings.httpAdminRoot, basicAuthMiddleware(settings.httpAdminAuth.user,settings.httpAdminAuth.pass));
} }
@ -243,10 +242,10 @@ RED.start().then(function() {
if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) { if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
server.on('error', function(err) { server.on('error', function(err) {
if (err.errno === "EADDRINUSE") { if (err.errno === "EADDRINUSE") {
RED.log.error(log._("server.unable-to-listen", {listenpath:getListenPath()})); RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()}));
RED.log.error(log._("server.port-in-use")); RED.log.error(RED.log._("server.port-in-use"));
} else { } else {
RED.log.error(log._("server.uncaught-exception")); RED.log.error(RED.log._("server.uncaught-exception"));
if (err.stack) { if (err.stack) {
RED.log.error(err.stack); RED.log.error(err.stack);
} else { } else {
@ -257,16 +256,16 @@ RED.start().then(function() {
}); });
server.listen(settings.uiPort,settings.uiHost,function() { server.listen(settings.uiPort,settings.uiHost,function() {
if (settings.httpAdminRoot === false) { if (settings.httpAdminRoot === false) {
RED.log.info(log._("server.admin-ui-disabled")); RED.log.info(RED.log._("server.admin-ui-disabled"));
} }
process.title = 'node-red'; process.title = 'node-red';
RED.log.info(log._("server.now-running", {listenpath:getListenPath()})); RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
}); });
} else { } else {
RED.log.info(log._("server.headless-mode")); RED.log.info(RED.log._("server.headless-mode"));
} }
}).otherwise(function(err) { }).otherwise(function(err) {
RED.log.error(log._("server.failed-to-start")); RED.log.error(RED.log._("server.failed-to-start"));
if (err.stack) { if (err.stack) {
RED.log.error(err.stack); RED.log.error(err.stack);
} else { } else {

View File

@ -25,7 +25,7 @@ var permissions = require("./permissions");
var theme = require("../theme"); var theme = require("../theme");
var settings = null; var settings = null;
var log = require("../../log"); var log = null
passport.use(strategies.bearerStrategy.BearerStrategy); passport.use(strategies.bearerStrategy.BearerStrategy);
@ -36,11 +36,13 @@ var server = oauth2orize.createServer();
server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange)); server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange));
function init(_settings,storage) { function init(runtime) {
settings = _settings; settings = runtime.settings;
log = runtime.log;
if (settings.adminAuth) { if (settings.adminAuth) {
Users.init(settings.adminAuth); Users.init(settings.adminAuth);
Tokens.init(settings.adminAuth,storage); Tokens.init(settings.adminAuth,runtime.storage);
strategies.init(runtime);
} }
} }

View File

@ -26,7 +26,7 @@ var Users = require("./users");
var Clients = require("./clients"); var Clients = require("./clients");
var permissions = require("./permissions"); var permissions = require("./permissions");
var log = require("../../log"); var log;
var bearerStrategy = function (accessToken, done) { var bearerStrategy = function (accessToken, done) {
// is this a valid token? // is this a valid token?
@ -124,6 +124,9 @@ AnonymousStrategy.prototype.authenticate = function(req) {
} }
module.exports = { module.exports = {
init: function(runtime) {
log = runtime.log;
},
bearerStrategy: bearerStrategy, bearerStrategy: bearerStrategy,
clientPasswordStrategy: clientPasswordStrategy, clientPasswordStrategy: clientPasswordStrategy,
passwordTokenExchange: passwordTokenExchange, passwordTokenExchange: passwordTokenExchange,

View File

@ -14,10 +14,14 @@
* limitations under the License. * limitations under the License.
**/ **/
var log = require("../log"); var log;
var api = require("../nodes"); var api;
module.exports = { module.exports = {
init: function(runtime) {
log = runtime.log;
api = runtime.api;
},
get: function (req, res) { get: function (req, res) {
// TODO: It should verify the given node id is of the type specified - // TODO: It should verify the given node id is of the type specified -
// but that would add a dependency from this module to the // but that would add a dependency from this module to the

View File

@ -14,12 +14,16 @@
* limitations under the License. * limitations under the License.
**/ **/
var log = require("../log"); var log;
var redNodes;
var redNodes = require("../nodes"); var settings;
var settings = require("../settings");
module.exports = { module.exports = {
init: function(runtime) {
settings = runtime.settings;
redNodes = runtime.api;
log = runtime.log;
},
get: function(req,res) { get: function(req,res) {
log.audit({event: "flows.get"},req); log.audit({event: "flows.get"},req);
res.json(redNodes.getFlows()); res.json(redNodes.getFlows());

View File

@ -29,12 +29,12 @@ var theme = require("./theme");
var locales = require("./locales"); var locales = require("./locales");
var credentials = require("./credentials"); var credentials = require("./credentials");
var log = require("../log");
var auth = require("./auth"); var auth = require("./auth");
var needsPermission = auth.needsPermission; var needsPermission = auth.needsPermission;
var settings = require("../settings"); var log;
var adminApp;
var nodeApp;
var errorHandler = function(err,req,res,next) { var errorHandler = function(err,req,res,next) {
if (err.message === "request entity too large") { if (err.message === "request entity too large") {
@ -46,71 +46,92 @@ var errorHandler = function(err,req,res,next) {
res.status(400).json({error:"unexpected_error", message:err.toString()}); res.status(400).json({error:"unexpected_error", message:err.toString()});
}; };
function init(adminApp,storage) { function init(runtime) {
var settings = runtime.settings;
log = runtime.log;
if (settings.httpNodeRoot !== false) {
nodeApp = express();
}
if (settings.httpAdminRoot !== false) {
adminApp = express();
auth.init(runtime);
credentials.init(runtime);
flows.init(runtime);
info.init(runtime);
library.init(adminApp,runtime);
locales.init(runtime);
nodes.init(runtime);
auth.init(settings,storage); // Editor
// Editor if (!settings.disableEditor) {
if (!settings.disableEditor) { ui.init(runtime);
ui.init(settings); var editorApp = express();
var editorApp = express(); editorApp.get("/",ui.ensureSlash,ui.editor);
editorApp.get("/",ui.ensureSlash,ui.editor); editorApp.get("/icons/:icon",ui.icon);
editorApp.get("/icons/:icon",ui.icon); if (settings.editorTheme) {
if (settings.editorTheme) { editorApp.use("/theme",theme.init(runtime));
editorApp.use("/theme",theme.init(settings)); }
editorApp.use("/",ui.editorResources);
adminApp.use(editorApp);
} }
editorApp.use("/",ui.editorResources); var maxApiRequestSize = settings.apiMaxLength || '1mb';
adminApp.use(editorApp); adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}));
adminApp.get("/auth/login",auth.login);
if (settings.adminAuth) {
//TODO: all passport references ought to be in ./auth
adminApp.use(passport.initialize());
adminApp.post("/auth/token",
auth.ensureClientSecret,
auth.authenticateClient,
auth.getToken,
auth.errorHandler
);
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke);
}
// Flows
adminApp.get("/flows",needsPermission("flows.read"),flows.get);
adminApp.post("/flows",needsPermission("flows.write"),flows.post);
// Nodes
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll);
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post);
adminApp.get("/nodes/:mod",needsPermission("nodes.read"),nodes.getModule);
adminApp.put("/nodes/:mod",needsPermission("nodes.write"),nodes.putModule);
adminApp.delete("/nodes/:mod",needsPermission("nodes.write"),nodes.delete);
adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet);
adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet);
adminApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get);
adminApp.get(/locales\/(.+)\/?$/,locales.get);
// Library
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post);
adminApp.get("/library/flows",needsPermission("library.read"),library.getAll);
adminApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get);
// Settings
adminApp.get("/settings",needsPermission("settings.read"),info.settings);
// Error Handler
adminApp.use(errorHandler);
} }
var maxApiRequestSize = settings.apiMaxLength || '1mb';
adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}));
adminApp.get("/auth/login",auth.login);
if (settings.adminAuth) {
//TODO: all passport references ought to be in ./auth
adminApp.use(passport.initialize());
adminApp.post("/auth/token",
auth.ensureClientSecret,
auth.authenticateClient,
auth.getToken,
auth.errorHandler
);
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke);
}
// Flows
adminApp.get("/flows",needsPermission("flows.read"),flows.get);
adminApp.post("/flows",needsPermission("flows.write"),flows.post);
// Nodes
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll);
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post);
adminApp.get("/nodes/:mod",needsPermission("nodes.read"),nodes.getModule);
adminApp.put("/nodes/:mod",needsPermission("nodes.write"),nodes.putModule);
adminApp.delete("/nodes/:mod",needsPermission("nodes.write"),nodes.delete);
adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet);
adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet);
adminApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get);
adminApp.get(/locales\/(.+)\/?$/,locales.get);
// Library
library.init(adminApp);
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post);
adminApp.get("/library/flows",needsPermission("library.read"),library.getAll);
adminApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get);
// Settings
adminApp.get("/settings",needsPermission("settings.read"),info.settings);
// Error Handler
adminApp.use(errorHandler);
} }
module.exports = { module.exports = {
init: init init: init,
library: {
register: library.register
},
auth: {
needsPermission: auth.needsPermission
},
adminApp: function() { return adminApp; },
nodeApp: function() { return nodeApp; }
}; };

View File

@ -13,28 +13,30 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var settings = require('../settings');
var theme = require("./theme"); var theme = require("./theme");
var util = require('util'); var util = require('util');
var settings;
module.exports = { module.exports = {
init: function(runtime) {
settings = runtime.settings;
},
settings: function(req,res) { settings: function(req,res) {
var safeSettings = { var safeSettings = {
httpNodeRoot: settings.httpNodeRoot, httpNodeRoot: settings.httpNodeRoot,
version: settings.version, version: settings.version,
user: req.user user: req.user
} }
var themeSettings = theme.settings(); var themeSettings = theme.settings();
if (themeSettings) { if (themeSettings) {
safeSettings.editorTheme = themeSettings; safeSettings.editorTheme = themeSettings;
} }
if (util.isArray(settings.paletteCategories)) { if (util.isArray(settings.paletteCategories)) {
safeSettings.paletteCategories = settings.paletteCategories; safeSettings.paletteCategories = settings.paletteCategories;
} }
res.json(safeSettings); res.json(safeSettings);
} }
} }

View File

@ -15,9 +15,8 @@
**/ **/
var redApp = null; var redApp = null;
var storage = require("../storage"); var storage;
var log = require("../log"); var log;
var needsPermission = require("./auth").needsPermission; var needsPermission = require("./auth").needsPermission;
function createLibrary(type) { function createLibrary(type) {
@ -70,8 +69,10 @@ function createLibrary(type) {
} }
} }
module.exports = { module.exports = {
init: function(app) { init: function(app,runtime) {
redApp = app; redApp = app;
log = runtime.log;
storage = runtime.storage;
}, },
register: createLibrary, register: createLibrary,
@ -90,7 +91,7 @@ module.exports = {
}).otherwise(function(err) { }).otherwise(function(err) {
if (err) { if (err) {
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()})); log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
if (err.code === 'forbidden') { if (err.code === 'forbidden') {
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req); log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
res.status(403).end(); res.status(403).end();
return; return;

View File

@ -13,9 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var i18n = require("../i18n"); var i18n;
module.exports = { module.exports = {
init: function(runtime) {
i18n = runtime.i18n;
},
get: function(req,res) { get: function(req,res) {
var namespace = req.params[0]; var namespace = req.params[0];
namespace = namespace.replace(/\.json$/,""); namespace = namespace.replace(/\.json$/,"");

View File

@ -13,16 +13,22 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var redNodes = require("../nodes");
var comms = require("../comms");
var log = require("../log");
var i18n = require("../i18n");
var when = require("when"); var when = require("when");
var redNodes;
var settings = require("../settings"); var comms;
var log;
var i18n;
var settings;
module.exports = { module.exports = {
init: function(runtime) {
redNodes = runtime.api;
comms = runtime.comms;
log = runtime.log;
i18n = runtime.i18n;
settings = runtime.settings;
},
getAll: function(req,res) { getAll: function(req,res) {
if (req.get("accept") == "application/json") { if (req.get("accept") == "application/json") {
log.audit({event: "nodes.list.get"},req); log.audit({event: "nodes.list.get"},req);

View File

@ -53,7 +53,8 @@ function serveFile(app,baseUrl,file) {
} }
module.exports = { module.exports = {
init: function(settings) { init: function(runtime) {
var settings = runtime.settings;
var i; var i;
var url; var url;
themeContext = clone(defaultContext); themeContext = clone(defaultContext);

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2014 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,26 +21,26 @@ var theme = require("./theme");
var Mustache = require("mustache"); var Mustache = require("mustache");
var events = require("../events");
var settings;
var icon_paths = [path.resolve(__dirname + '/../../public/icons')]; var icon_paths = [path.resolve(__dirname + '/../../public/icons')];
var iconCache = {}; var iconCache = {};
//TODO: create a default icon //TODO: create a default icon
var defaultIcon = path.resolve(__dirname + '/../../public/icons/arrow-in.png'); var defaultIcon = path.resolve(__dirname + '/../../public/icons/arrow-in.png');
events.on("node-icon-dir",function(dir) {
icon_paths.push(path.resolve(dir));
});
var templateDir = path.resolve(__dirname+"/../../editor/templates"); var templateDir = path.resolve(__dirname+"/../../editor/templates");
var editorTemplate; var editorTemplate;
function nodeIconDir(dir) {
icon_paths.push(path.resolve(dir));
}
module.exports = { module.exports = {
init: function(_settings) { init: function(runtime) {
settings = _settings;
editorTemplate = fs.readFileSync(path.join(templateDir,"index.mst"),"utf8"); editorTemplate = fs.readFileSync(path.join(templateDir,"index.mst"),"utf8");
Mustache.parse(editorTemplate); Mustache.parse(editorTemplate);
// TODO: this allows init to be called multiple times without
// registering multiple instances of the listener.
// It isn't.... ideal.
runtime.events.removeListener("node-icon-dir",nodeIconDir);
runtime.events.on("node-icon-dir",nodeIconDir);
}, },
ensureSlash: function(req,res,next) { ensureSlash: function(req,res,next) {

View File

@ -14,22 +14,18 @@
* limitations under the License. * limitations under the License.
**/ **/
var server = require("./server");
var nodes = require("./nodes");
var library = require("./api/library");
var comms = require("./comms");
var log = require("./log");
var util = require("./util");
var i18n = require("./i18n");
var fs = require("fs"); var fs = require("fs");
var settings = require("./settings");
var credentials = require("./nodes/credentials");
var auth = require("./api/auth");
var path = require('path'); var path = require('path');
var events = require("events");
var runtime = require("./runtime");
var api = require("./api");
process.env.NODE_RED_HOME = process.env.NODE_RED_HOME || path.resolve(__dirname+"/.."); process.env.NODE_RED_HOME = process.env.NODE_RED_HOME || path.resolve(__dirname+"/..");
var nodeApp = null;
var adminApp = null;
var server = null;
function checkBuild() { function checkBuild() {
var editorFile = path.resolve(path.join(__dirname,"..","public","red","red.min.js")); var editorFile = path.resolve(path.join(__dirname,"..","public","red","red.min.js"));
try { try {
@ -43,40 +39,44 @@ function checkBuild() {
var RED = { var RED = {
init: function(httpServer,userSettings) { init: function(httpServer,userSettings) {
server = httpServer;
if (!userSettings.SKIP_BUILD_CHECK) { if (!userSettings.SKIP_BUILD_CHECK) {
checkBuild(); checkBuild();
} }
userSettings.version = this.version(); runtime.init(httpServer,userSettings);
log.init(userSettings); if (userSettings.httpAdminRoot !== false || userSettings.httpNodeRoot !== false) {
settings.init(userSettings); api.init(runtime);
server.init(httpServer,settings); adminApp = api.adminApp();
return server.app; nodeApp = api.nodeApp();
},
start: server.start,
stop: server.stop,
nodes: nodes,
library: { register: library.register },
credentials: credentials,
events: events,
log: log,
comms: comms,
settings:settings,
util: util,
auth: {
needsPermission: auth.needsPermission
},
version: function () {
var p = require(path.join(process.env.NODE_RED_HOME,"package.json")).version;
/* istanbul ignore else */
if (fs.existsSync(path.join(process.env.NODE_RED_HOME,".git"))) {
p += "-git";
} }
return p;
if (adminApp === null) {
adminApp = {
get:function(){},
post: function(){},
put: function(){},
delete: function(){}
}
}
return runtime.app;
}, },
get app() { console.log("Deprecated use of RED.app - use RED.httpAdmin instead"); return server.app }, start: runtime.start,
get httpAdmin() { return server.app }, stop: runtime.stop,
get httpNode() { return server.nodeApp }, nodes: runtime.api,
get server() { return server.server } events: runtime.events,
log: runtime.log,
comms: runtime.comms,
settings:runtime.settings,
util: runtime.util,
version: runtime.version,
library: api.library,
auth: api.auth,
get app() { console.log("Deprecated use of RED.app - use RED.httpAdmin instead"); return runtime.app },
get httpAdmin() { return adminApp },
get httpNode() { return nodeApp },
get server() { return server }
}; };
module.exports = RED; module.exports = RED;

View File

@ -37,9 +37,9 @@ function init(_server,_settings) {
function start() { function start() {
var Tokens = require("./api/auth/tokens"); var Tokens = require("../api/auth/tokens");
var Users = require("./api/auth/users"); var Users = require("../api/auth/users");
var Permissions = require("./api/auth/permissions"); var Permissions = require("../api/auth/permissions");
if (!settings.disableEditor) { if (!settings.disableEditor) {
Users.default().then(function(anonymousUser) { Users.default().then(function(anonymousUser) {
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000; var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;

View File

@ -24,11 +24,11 @@ var supportedLangs = [];
var resourceMap = { var resourceMap = {
"runtime": { "runtime": {
basedir: path.resolve(__dirname+"/../locales"), basedir: path.resolve(__dirname+"/../../locales"),
file:"runtime.json" file:"runtime.json"
}, },
"editor": { "editor": {
basedir: path.resolve(__dirname+"/../locales"), basedir: path.resolve(__dirname+"/../../locales"),
file: "editor.json" file: "editor.json"
} }
} }

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var express = require('express');
var when = require('when'); var when = require('when');
var redNodes = require("./nodes"); var redNodes = require("./nodes");
@ -22,23 +21,27 @@ var comms = require("./comms");
var storage = require("./storage"); var storage = require("./storage");
var log = require("./log"); var log = require("./log");
var i18n = require("./i18n"); var i18n = require("./i18n");
var events = require("./events");
var app = null; var settings = require("./settings");
var nodeApp = null; var path = require('path');
var server = null; var fs = require("fs");
var settings = null;
var runtimeMetricInterval = null; var runtimeMetricInterval = null;
function init(server,userSettings) {
userSettings.version = version();
log.init(userSettings);
settings.init(userSettings);
comms.init(server,settings);
}
function init(_server,_settings) { function version() {
server = _server; var p = require(path.join(process.env.NODE_RED_HOME,"package.json")).version;
settings = _settings; /* istanbul ignore else */
if (fs.existsSync(path.join(process.env.NODE_RED_HOME,".git"))) {
comms.init(_server,_settings); p += "-git";
}
nodeApp = express(); return p;
app = express();
} }
function start() { function start() {
@ -46,9 +49,6 @@ function start() {
.then(function() { return storage.init(settings)}) .then(function() { return storage.init(settings)})
.then(function() { return settings.load(storage)}) .then(function() { return settings.load(storage)})
.then(function() { .then(function() {
if (settings.httpAdminRoot !== false) {
require("./api").init(app,storage);
}
if (log.metric()) { if (log.metric()) {
runtimeMetricInterval = setInterval(function() { runtimeMetricInterval = setInterval(function() {
@ -141,12 +141,19 @@ function stop() {
comms.stop(); comms.stop();
} }
var serverAPI = module.exports = { var runtime = module.exports = {
init: init, init: init,
start: start, start: start,
stop: stop, stop: stop,
get app() { return app }, version: version,
get nodeApp() { return nodeApp },
get server() { return server } log: log,
i18n: i18n,
settings: settings,
storage: storage,
comms: comms,
events: events,
api: redNodes,
util: require("./util")
} }

View File

@ -18,8 +18,6 @@ var when = require("when");
var log = require("../log"); var log = require("../log");
var needsPermission = require("../api/auth").needsPermission;
var credentialCache = {}; var credentialCache = {};
var storage = null; var storage = null;
var credentialsDef = {}; var credentialsDef = {};

View File

@ -36,9 +36,7 @@ events.on("node-locales-dir", function(info) {
function init(_settings) { function init(_settings) {
settings = _settings; settings = _settings;
localfilesystem.init(settings); localfilesystem.init(settings);
RED = require('../../../red');
RED = require('../../red');
} }
function load(defaultNodesDir,disableNodePathScan) { function load(defaultNodesDir,disableNodePathScan) {

View File

@ -22,7 +22,7 @@ var events = require("../../events");
var log = require("../../log"); var log = require("../../log");
var settings; var settings;
var defaultNodesDir = path.resolve(path.join(__dirname,"..","..","..","nodes")); var defaultNodesDir = path.resolve(path.join(__dirname,"..","..","..","..","nodes"));
var disableNodePathScan = false; var disableNodePathScan = false;
function init(_settings,_defaultNodesDir,_disableNodePathScan) { function init(_settings,_defaultNodesDir,_disableNodePathScan) {
@ -133,7 +133,7 @@ function scanDirForNodesModules(dir,moduleName) {
* @return a list of node modules: {dir,package} * @return a list of node modules: {dir,package}
*/ */
function scanTreeForNodesModules(moduleName) { function scanTreeForNodesModules(moduleName) {
var dir = __dirname+"/../../nodes"; var dir = __dirname+"/../../../../nodes";
var results = []; var results = [];
var userDir; var userDir;

View File

@ -256,7 +256,7 @@ function getFullNodeInfo(typeOrId) {
var module = moduleConfigs[getModule(id)]; var module = moduleConfigs[getModule(id)];
if (module) { if (module) {
return module.nodes[getNode(id)]; return module.nodes[getNode(id)];
} }
} }
return null; return null;
} }
@ -292,7 +292,7 @@ function getModuleList() {
//} //}
//return list; //return list;
return moduleConfigs; return moduleConfigs;
} }
function getModuleInfo(module) { function getModuleInfo(module) {
@ -358,7 +358,7 @@ function getNodeConfig(id,lang) {
if (config) { if (config) {
var result = config.config; var result = config.config;
result += loader.getNodeHelp(config,lang||"en-US") result += loader.getNodeHelp(config,lang||"en-US")
//if (config.script) { //if (config.script) {
// result += '<script type="text/javascript">'+config.script+'</script>'; // result += '<script type="text/javascript">'+config.script+'</script>';
//} //}
@ -490,9 +490,9 @@ var registry = module.exports = {
addNodeSet: addNodeSet, addNodeSet: addNodeSet,
enableNodeSet: enableNodeSet, enableNodeSet: enableNodeSet,
disableNodeSet: disableNodeSet, disableNodeSet: disableNodeSet,
removeModule: removeModule, removeModule: removeModule,
getNodeInfo: getNodeInfo, getNodeInfo: getNodeInfo,
getFullNodeInfo: getFullNodeInfo, getFullNodeInfo: getFullNodeInfo,
getNodeList: getNodeList, getNodeList: getNodeList,

View File

@ -24,12 +24,14 @@ var auth = require("../../../../red/api/auth");
var Users = require("../../../../red/api/auth/users"); var Users = require("../../../../red/api/auth/users");
var Tokens = require("../../../../red/api/auth/tokens"); var Tokens = require("../../../../red/api/auth/tokens");
var settings = require("../../../../red/settings");
describe("api auth middleware",function() { describe("api auth middleware",function() {
describe("ensureClientSecret", function() { describe("ensureClientSecret", function() {
before(function() {
auth.init({settings:{},log:{audit:function(){}}})
});
it("leaves client_secret alone if not present",function(done) { it("leaves client_secret alone if not present",function(done) {
var req = { var req = {
body: { body: {
@ -83,7 +85,7 @@ describe("api auth middleware",function() {
Users.init.restore(); Users.init.restore();
}); });
it("returns login details - credentials", function(done) { it("returns login details - credentials", function(done) {
auth.init({adminAuth:{}},null); auth.init({settings:{adminAuth:{}},log:{audit:function(){}}})
auth.login(null,{json: function(resp) { auth.login(null,{json: function(resp) {
resp.should.have.a.property("type","credentials"); resp.should.have.a.property("type","credentials");
resp.should.have.a.property("prompts"); resp.should.have.a.property("prompts");
@ -92,7 +94,7 @@ describe("api auth middleware",function() {
}}); }});
}); });
it("returns login details - none", function(done) { it("returns login details - none", function(done) {
auth.init({},null); auth.init({settings:{},log:{audit:function(){}}})
auth.login(null,{json: function(resp) { auth.login(null,{json: function(resp) {
resp.should.eql({}); resp.should.eql({});
done(); done();

View File

@ -24,8 +24,10 @@ var Tokens = require("../../../../red/api/auth/tokens");
var Clients = require("../../../../red/api/auth/clients"); var Clients = require("../../../../red/api/auth/clients");
describe("Auth strategies", function() { describe("Auth strategies", function() {
before(function() {
strategies.init({log:{audit:function(){}}})
});
describe("Password Token Exchange", function() { describe("Password Token Exchange", function() {
var userAuthentication; var userAuthentication;
afterEach(function() { afterEach(function() {
if (userAuthentication) { if (userAuthentication) {
@ -49,7 +51,7 @@ describe("Auth strategies", function() {
} }
}); });
}); });
it('Handles scope overreach',function(done) { it('Handles scope overreach',function(done) {
userAuthentication = sinon.stub(Users,"authenticate",function(username,password) { userAuthentication = sinon.stub(Users,"authenticate",function(username,password) {
return when.resolve({username:"user",permissions:"read"}); return when.resolve({username:"user",permissions:"read"});

View File

@ -20,8 +20,6 @@ var express = require('express');
var sinon = require('sinon'); var sinon = require('sinon');
var when = require('when'); var when = require('when');
var nodeApi = require("../../../red/nodes");
var credentials = require("../../../red/api/credentials"); var credentials = require("../../../red/api/credentials");
describe('credentials api', function() { describe('credentials api', function() {
@ -30,26 +28,26 @@ describe('credentials api', function() {
before(function() { before(function() {
app = express(); app = express();
app.get('/credentials/:type/:id',credentials.get); app.get('/credentials/:type/:id',credentials.get);
credentials.init({
sinon.stub(nodeApi,"getCredentials",function(id) { log:{audit:function(){}},
if (id === "n1") { api:{
return {user1:"abc",password1:"123"}; getCredentials: function(id) {
} else { if (id === "n1") {
return null; return {user1:"abc",password1:"123"};
} } else {
}); return null;
sinon.stub(nodeApi,"getCredentialDefinition",function(type) { }
if (type === "known-type") { },
return {user1:{type:"text"},password1:{type:"password"}}; getCredentialDefinition:function(type) {
} else { if (type === "known-type") {
return null; return {user1:{type:"text"},password1:{type:"password"}};
} else {
return null;
}
}
} }
}); });
}); });
after(function() {
nodeApi.getCredentials.restore();
nodeApi.getCredentialDefinition.restore();
})
it('returns empty credentials if unknown type',function(done) { it('returns empty credentials if unknown type',function(done) {
request(app) request(app)
.get("/credentials/unknown-type/n1") .get("/credentials/unknown-type/n1")

View File

@ -21,8 +21,6 @@ var bodyParser = require('body-parser');
var sinon = require('sinon'); var sinon = require('sinon');
var when = require('when'); var when = require('when');
var redNodes = require("../../../red/nodes");
var flows = require("../../../red/api/flows"); var flows = require("../../../red/api/flows");
describe("flows api", function() { describe("flows api", function() {
@ -37,15 +35,17 @@ describe("flows api", function() {
}); });
it('returns flow', function(done) { it('returns flow', function(done) {
var getFlows = sinon.stub(redNodes,'getFlows', function() { flows.init({
return [1,2,3]; log:{warn:function(){},_:function(){},audit:function(){}},
api:{
getFlows: function() { return [1,2,3]; }
}
}); });
request(app) request(app)
.get('/flows') .get('/flows')
.set('Accept', 'application/json') .set('Accept', 'application/json')
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
getFlows.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -55,15 +55,17 @@ describe("flows api", function() {
}); });
it('sets flows', function(done) { it('sets flows', function(done) {
var setFlows = sinon.stub(redNodes,'setFlows', function() { flows.init({
return when.resolve(); log:{warn:function(){},_:function(){},audit:function(){}},
api:{
setFlows: function() { return when.resolve(); }
}
}); });
request(app) request(app)
.post('/flows') .post('/flows')
.set('Accept', 'application/json') .set('Accept', 'application/json')
.expect(204) .expect(204)
.end(function(err,res) { .end(function(err,res) {
setFlows.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -71,15 +73,17 @@ describe("flows api", function() {
}); });
}); });
it('returns error when set fails', function(done) { it('returns error when set fails', function(done) {
var setFlows = sinon.stub(redNodes,'setFlows', function() { flows.init({
return when.reject(new Error("expected error")); log:{warn:function(){},_:function(){},audit:function(){}},
api:{
setFlows: function() { return when.reject(new Error("expected error")); }
}
}); });
request(app) request(app)
.post('/flows') .post('/flows')
.set('Accept', 'application/json') .set('Accept', 'application/json')
.expect(500) .expect(500)
.end(function(err,res) { .end(function(err,res) {
setFlows.restore();
if (err) { if (err) {
throw err; throw err;
} }

View File

@ -15,12 +15,12 @@
**/ **/
var should = require("should"); var should = require("should");
var sinon = require("sinon");
var request = require("supertest"); var request = require("supertest");
var express = require("express"); var express = require("express");
var when = require("when"); var when = require("when");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var settings = require("../../../red/settings");
var api = require("../../../red/api"); var api = require("../../../red/api");
describe("api index", function() { describe("api index", function() {
@ -28,12 +28,13 @@ describe("api index", function() {
describe("disables editor", function() { describe("disables editor", function() {
before(function() { before(function() {
settings.init({disableEditor:true});
app = express(); app = express();
api.init(app); api.init(app,{
}); settings:{disableEditor:true},
after(function() { api:{
settings.reset();
}
});
}); });
it('does not serve the editor', function(done) { it('does not serve the editor', function(done) {
@ -54,14 +55,25 @@ describe("api index", function() {
}); });
describe("can serve auth", function() { describe("can serve auth", function() {
var mockList = [
'ui','nodes','flows','library','info','theme','locales','credentials'
]
before(function() { before(function() {
//settings.init({disableEditor:true}); mockList.forEach(function(m) {
settings.init({adminAuth:{type: "credentials",users:[],default:{permissions:"read"}}}); sinon.stub(require("../../../red/api/"+m),"init",function(){});
app = express(); });
api.init(app,{getSessions:function(){return when.resolve({})}});
}); });
after(function() { after(function() {
settings.reset(); mockList.forEach(function(m) {
require("../../../red/api/"+m).init.restore();
})
});
before(function() {
app = express();
api.init(app,{
settings:{adminAuth:{type: "credentials",users:[],default:{permissions:"read"}}},
storage:{getSessions:function(){return when.resolve({})}}
});
}); });
it('it now serves auth', function(done) { it('it now serves auth', function(done) {
@ -77,15 +89,29 @@ describe("api index", function() {
}); });
describe("enables editor", function() { describe("enables editor", function() {
var mockList = [
'nodes','flows','library','info','theme','locales','credentials'
]
before(function() { before(function() {
settings.init({disableEditor:false}); mockList.forEach(function(m) {
app = express(); sinon.stub(require("../../../red/api/"+m),"init",function(){});
api.init(app); });
}); });
after(function() { after(function() {
settings.reset(); mockList.forEach(function(m) {
require("../../../red/api/"+m).init.restore();
})
}); });
before(function() {
app = express();
api.init(app,{
log:{audit:function(){}},
settings:{disableEditor:false},
events:{on:function(){},removeListener:function(){}}
});
});
it('serves the editor', function(done) { it('serves the editor', function(done) {
request(app) request(app)
.get("/") .get("/")

View File

@ -21,30 +21,26 @@ var sinon = require('sinon');
var when = require('when'); var when = require('when');
var app = express(); var app = express();
var settings = require("../../../red/settings");
var info = require("../../../red/api/info"); var info = require("../../../red/api/info");
var theme = require("../../../red/api/theme"); var theme = require("../../../red/api/theme");
describe("info api", function() { describe("info api", function() {
describe("settings handler", function() { describe("settings handler", function() {
before(function() { before(function() {
var userSettings = {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"]
}
settings.init(userSettings);
sinon.stub(theme,"settings",function() { return { test: 456 };}); sinon.stub(theme,"settings",function() { return { test: 456 };});
info.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"]
}
})
app = express(); app = express();
app.get("/settings",info.settings); app.get("/settings",info.settings);
}); });
after(function() { after(function() {
settings.reset();
theme.settings.restore(); theme.settings.restore();
}); });

View File

@ -21,19 +21,18 @@ var bodyParser = require('body-parser');
var when = require('when'); var when = require('when');
var app = express(); var app;
var RED = require("../../../red/red.js");
var storage = require("../../../red/storage");
var library = require("../../../red/api/library"); var library = require("../../../red/api/library");
var auth = require("../../../red/api/auth"); var auth = require("../../../red/api/auth");
describe("library api", function() { describe("library api", function() {
function initStorage(_flows,_libraryEntries) { function initLibrary(_flows,_libraryEntries) {
var flows = _flows; var flows = _flows;
var libraryEntries = _libraryEntries; var libraryEntries = _libraryEntries;
storage.init({ library.init(app,{
storageModule: { log:{audit:function(){},_:function(){},warn:function(){}},
storage: {
init: function() { init: function() {
return when.resolve(); return when.resolve();
}, },
@ -43,15 +42,29 @@ describe("library api", function() {
getFlow: function(fn) { getFlow: function(fn) {
if (flows[fn]) { if (flows[fn]) {
return when.resolve(flows[fn]); return when.resolve(flows[fn]);
} else if (fn.indexOf("..")!==-1) {
var err = new Error();
err.code = 'forbidden';
return when.reject(err);
} else { } else {
return when.reject(); return when.reject();
} }
}, },
saveFlow: function(fn,data) { saveFlow: function(fn,data) {
if (fn.indexOf("..")!==-1) {
var err = new Error();
err.code = 'forbidden';
return when.reject(err);
}
flows[fn] = data; flows[fn] = data;
return when.resolve(); return when.resolve();
}, },
getLibraryEntry: function(type,path) { getLibraryEntry: function(type,path) {
if (path.indexOf("..")!==-1) {
var err = new Error();
err.code = 'forbidden';
return when.reject(err);
}
if (libraryEntries[type] && libraryEntries[type][path]) { if (libraryEntries[type] && libraryEntries[type][path]) {
return when.resolve(libraryEntries[type][path]); return when.resolve(libraryEntries[type][path]);
} else { } else {
@ -59,6 +72,11 @@ describe("library api", function() {
} }
}, },
saveLibraryEntry: function(type,path,meta,body) { saveLibraryEntry: function(type,path,meta,body) {
if (path.indexOf("..")!==-1) {
var err = new Error();
err.code = 'forbidden';
return when.reject(err);
}
libraryEntries[type][path] = body; libraryEntries[type][path] = body;
return when.resolve(); return when.resolve();
} }
@ -67,8 +85,6 @@ describe("library api", function() {
} }
describe("flows", function() { describe("flows", function() {
var app;
before(function() { before(function() {
app = express(); app = express();
app.use(bodyParser.json()); app.use(bodyParser.json());
@ -77,7 +93,7 @@ describe("library api", function() {
app.get(new RegExp("/library/flows\/(.*)"),library.get); app.get(new RegExp("/library/flows\/(.*)"),library.get);
}); });
it('returns empty result', function(done) { it('returns empty result', function(done) {
initStorage({},{flows:{}}); initLibrary({},{flows:{}});
request(app) request(app)
.get('/library/flows') .get('/library/flows')
.expect(200) .expect(200)
@ -92,7 +108,7 @@ describe("library api", function() {
}); });
it('returns 404 for non-existent entry', function(done) { it('returns 404 for non-existent entry', function(done) {
initStorage({},{flows:{}}); initLibrary({},{flows:{}});
request(app) request(app)
.get('/library/flows/foo') .get('/library/flows/foo')
.expect(404) .expect(404)
@ -101,7 +117,7 @@ describe("library api", function() {
it('can store and retrieve item', function(done) { it('can store and retrieve item', function(done) {
initStorage({},{flows:{}}); initLibrary({},{flows:{}});
var flow = '[]'; var flow = '[]';
request(app) request(app)
.post('/library/flows/foo') .post('/library/flows/foo')
@ -125,7 +141,7 @@ describe("library api", function() {
}); });
it('lists a stored item', function(done) { it('lists a stored item', function(done) {
initStorage({f:["bar"]}); initLibrary({f:["bar"]});
request(app) request(app)
.get('/library/flows') .get('/library/flows')
.expect(200) .expect(200)
@ -140,7 +156,7 @@ describe("library api", function() {
}); });
it('returns 403 for malicious get attempt', function(done) { it('returns 403 for malicious get attempt', function(done) {
initStorage({}); initLibrary({});
// without the userDir override the malicious url would be // without the userDir override the malicious url would be
// http://127.0.0.1:1880/library/flows/../../package to // http://127.0.0.1:1880/library/flows/../../package to
// obtain package.json from the node-red root. // obtain package.json from the node-red root.
@ -150,7 +166,7 @@ describe("library api", function() {
.end(done); .end(done);
}); });
it('returns 403 for malicious post attempt', function(done) { it('returns 403 for malicious post attempt', function(done) {
initStorage({}); initLibrary({});
// without the userDir override the malicious url would be // without the userDir override the malicious url would be
// http://127.0.0.1:1880/library/flows/../../package to // http://127.0.0.1:1880/library/flows/../../package to
// obtain package.json from the node-red root. // obtain package.json from the node-red root.
@ -162,18 +178,17 @@ describe("library api", function() {
}); });
describe("type", function() { describe("type", function() {
var app;
before(function() { before(function() {
app = express(); app = express();
app.use(bodyParser.json()); app.use(bodyParser.json());
library.init(app); initLibrary({},{});
auth.init({}); auth.init({settings:{}});
RED.library.register("test"); library.register("test");
}); });
it('returns empty result', function(done) { it('returns empty result', function(done) {
initStorage({},{'test':{"":[]}}); initLibrary({},{'test':{"":[]}});
request(app) request(app)
.get('/library/test') .get('/library/test')
.expect(200) .expect(200)
@ -187,7 +202,7 @@ describe("library api", function() {
}); });
it('returns 404 for non-existent entry', function(done) { it('returns 404 for non-existent entry', function(done) {
initStorage({},{}); initLibrary({},{});
request(app) request(app)
.get('/library/test/foo') .get('/library/test/foo')
.expect(404) .expect(404)
@ -195,7 +210,7 @@ describe("library api", function() {
}); });
it('can store and retrieve item', function(done) { it('can store and retrieve item', function(done) {
initStorage({},{'test':{}}); initLibrary({},{'test':{}});
var flow = {text:"test content"}; var flow = {text:"test content"};
request(app) request(app)
.post('/library/test/foo') .post('/library/test/foo')
@ -219,7 +234,7 @@ describe("library api", function() {
}); });
it('lists a stored item', function(done) { it('lists a stored item', function(done) {
initStorage({},{'test':{'a':['abc','def']}}); initLibrary({},{'test':{'a':['abc','def']}});
request(app) request(app)
.get('/library/test/a') .get('/library/test/a')
.expect(200) .expect(200)

View File

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

View File

@ -21,7 +21,6 @@ var bodyParser = require('body-parser');
var sinon = require('sinon'); var sinon = require('sinon');
var when = require('when'); var when = require('when');
var redNodes = require("../../../red/nodes");
var settings = require("../../../red/settings"); var settings = require("../../../red/settings");
var nodes = require("../../../red/api/nodes"); var nodes = require("../../../red/api/nodes");
@ -29,6 +28,16 @@ var nodes = require("../../../red/api/nodes");
describe("nodes api", function() { describe("nodes api", function() {
var app; var app;
function initNodes(runtime) {
runtime.log = {
audit:function(e){},//console.log(e)},
_:function(){},
info: function(){},
warn: function(){}
}
runtime.comms = { publish:function(){}}
nodes.init(runtime);
}
before(function() { before(function() {
app = express(); app = express();
@ -44,15 +53,18 @@ describe("nodes api", function() {
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() { initNodes({
return [1,2,3]; api:{
getNodeList: function() {
return [1,2,3];
}
}
}); });
request(app) request(app)
.get('/nodes') .get('/nodes')
.set('Accept', 'application/json') .set('Accept', 'application/json')
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
getNodeList.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -62,8 +74,15 @@ describe("nodes api", function() {
}); });
it('returns node configs', function(done) { it('returns node configs', function(done) {
var getNodeConfigs = sinon.stub(redNodes,'getNodeConfigs', function() { initNodes({
return "<script></script>"; api:{
getNodeConfigs: function() {
return "<script></script>";
}
},
i18n: {
determineLangFromHeaders: function(){}
}
}); });
request(app) request(app)
.get('/nodes') .get('/nodes')
@ -71,7 +90,6 @@ describe("nodes api", function() {
.expect(200) .expect(200)
.expect("<script></script>") .expect("<script></script>")
.end(function(err,res) { .end(function(err,res) {
getNodeConfigs.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -80,14 +98,17 @@ describe("nodes api", function() {
}); });
it('returns node module info', function(done) { it('returns node module info', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getModuleInfo', function(id) { initNodes({
return {"node-red":{name:"node-red"}}[id]; api:{
getModuleInfo: function(id) {
return {"node-red":{name:"node-red"}}[id];
}
}
}); });
request(app) request(app)
.get('/nodes/node-red') .get('/nodes/node-red')
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
getNodeInfo.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -97,14 +118,17 @@ describe("nodes api", function() {
}); });
it('returns 404 for unknown module', function(done) { it('returns 404 for unknown module', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getModuleInfo', function(id) { initNodes({
return {"node-red":{name:"node-red"}}[id]; api:{
getModuleInfo: function(id) {
return {"node-red":{name:"node-red"}}[id];
}
}
}); });
request(app) request(app)
.get('/nodes/node-blue') .get('/nodes/node-blue')
.expect(404) .expect(404)
.end(function(err,res) { .end(function(err,res) {
getNodeInfo.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -113,15 +137,18 @@ describe("nodes api", function() {
}); });
it('returns individual node info', function(done) { it('returns individual node info', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo', function(id) { initNodes({
return {"node-red/123":{id:"node-red/123"}}[id]; api:{
getNodeInfo: function(id) {
return {"node-red/123":{id:"node-red/123"}}[id];
}
}
}); });
request(app) request(app)
.get('/nodes/node-red/123') .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) {
getNodeInfo.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -131,8 +158,15 @@ describe("nodes api", function() {
}); });
it('returns individual node configs', function(done) { it('returns individual node configs', function(done) {
var getNodeConfig = sinon.stub(redNodes,'getNodeConfig', function(id) { initNodes({
return {"node-red/123":"<script></script>"}[id]; api:{
getNodeConfig: function(id) {
return {"node-red/123":"<script></script>"}[id];
}
},
i18n: {
determineLangFromHeaders: function(){}
}
}); });
request(app) request(app)
.get('/nodes/node-red/123') .get('/nodes/node-red/123')
@ -140,7 +174,6 @@ describe("nodes api", function() {
.expect(200) .expect(200)
.expect("<script></script>") .expect("<script></script>")
.end(function(err,res) { .end(function(err,res) {
getNodeConfig.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -149,15 +182,18 @@ describe("nodes api", function() {
}); });
it('returns 404 for unknown node', function(done) { it('returns 404 for unknown node', function(done) {
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo', function(id) { initNodes({
return {"node-red/123":{id:"node-red/123"}}[id]; api:{
getNodeInfo: function(id) {
return {"node-red/123":{id:"node-red/123"}}[id];
}
}
}); });
request(app) request(app)
.get('/nodes/node-red/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) {
getNodeInfo.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -169,14 +205,13 @@ 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() { initNodes({
return false; settings:{available:function(){return false}}
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -185,15 +220,14 @@ describe("nodes api", function() {
}); });
it('returns 400 if request is invalid', function(done) { it('returns 400 if request is invalid', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}}
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({}) .send({})
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -203,26 +237,23 @@ describe("nodes api", function() {
describe('by module', function() { describe('by module', function() {
it('installs the module and returns module info', function(done) { it('installs the module and returns module info', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}},
api:{
getModuleInfo: function(id) { return null; },
installModule: function() {
return when.resolve({
name:"foo",
nodes:[{id:"123"}]
});
}
}
}); });
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo');
getModuleInfo.onCall(0).returns(null);
var installModule = sinon.stub(redNodes,'installModule', function() {
return when.resolve({
name:"foo",
nodes:[{id:"123"}]
});
});
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo'})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
installModule.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -234,24 +265,20 @@ describe("nodes api", function() {
}); });
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() { initNodes({
return true; settings:{available:function(){return true}},
api:{
getModuleInfo: function(id) { return {nodes:{id:"123"}}; },
installModule: function() {
return when.resolve({id:"123"});
}
}
}); });
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) {
return {nodes:{id:"123"}};
});
var installModule = sinon.stub(redNodes,'installModule', function() {
return when.resolve({id:"123"});
});
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo'})
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
installModule.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -260,24 +287,20 @@ describe("nodes api", function() {
}); });
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() { initNodes({
return true; settings:{available:function(){return true}},
api:{
getModuleInfo: function(id) { return null },
installModule: function() {
return when.reject(new Error("test error"));
}
}
}); });
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) {
return null;
});
var installModule = sinon.stub(redNodes,'installModule', function() {
return when.reject(new Error("test error"));
});
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo'})
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
installModule.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -286,26 +309,22 @@ describe("nodes api", function() {
}); });
}); });
it('fails the install if module not found', function(done) { it('fails the install if module not found', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}},
api:{
getModuleInfo: function(id) { return null },
installModule: function() {
var err = new Error("test error");
err.code = 404;
return when.reject(err);
}
}
}); });
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) {
return null;
});
var installModule = sinon.stub(redNodes,'installModule', function() {
var err = new Error("test error");
err.code = 404;
return when.reject(err);
});
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo'})
.expect(404) .expect(404)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
installModule.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -319,6 +338,10 @@ describe("nodes api", function() {
var settingsAvailable = sinon.stub(settings,'available', function() { var settingsAvailable = sinon.stub(settings,'available', function() {
return false; return false;
}); });
initNodes({
settings:{available:function(){return false}}
});
request(app) request(app)
.del('/nodes/123') .del('/nodes/123')
.expect(400) .expect(400)
@ -333,27 +356,18 @@ describe("nodes api", function() {
describe('by module', function() { describe('by module', function() {
it('uninstalls the module', function(done) { it('uninstalls the module', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}},
api:{
getModuleInfo: function(id) { return {nodes:[{id:"123"}]} },
getNodeInfo: function() { return null },
uninstallModule: function() { return when.resolve({id:"123"});}
}
}); });
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo',function(id) {
return null;
});
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) {
return {nodes:[{id:"123"}]};
});
var uninstallModule = sinon.stub(redNodes,'uninstallModule', function() {
return when.resolve({id:"123"});
});
request(app) request(app)
.del('/nodes/foo') .del('/nodes/foo')
.expect(204) .expect(204)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getNodeInfo.restore();
getModuleInfo.restore();
uninstallModule.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -362,23 +376,17 @@ describe("nodes api", function() {
}); });
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() { initNodes({
return true; settings:{available:function(){return true}},
api:{
getModuleInfo: function(id) { return null },
getNodeInfo: function() { return null }
}
}); });
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo',function(id) {
return null;
});
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) {
return null;
});
request(app) request(app)
.del('/nodes/foo') .del('/nodes/foo')
.expect(404) .expect(404)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getNodeInfo.restore();
getModuleInfo.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -387,27 +395,18 @@ describe("nodes api", function() {
}); });
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() { initNodes({
return true; settings:{available:function(){return true}},
api:{
getModuleInfo: function(id) { return {nodes:[{id:"123"}]} },
getNodeInfo: function() { return null },
uninstallModule: function() { return when.reject(new Error("test error"));}
}
}); });
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo',function(id) {
return null;
});
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) {
return {nodes:[{id:"123"}]};
});
var uninstallModule = sinon.stub(redNodes,'uninstallModule', function() {
return when.reject(new Error("test error"));
});
request(app) request(app)
.del('/nodes/foo') .del('/nodes/foo')
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getNodeInfo.restore();
getModuleInfo.restore();
uninstallModule.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -421,14 +420,13 @@ 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() { initNodes({
return false; settings:{available:function(){return false}}
}); });
request(app) request(app)
.put('/nodes/123') .put('/nodes/123')
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -437,16 +435,14 @@ describe("nodes api", function() {
}); });
it('returns 400 for invalid node payload', function(done) { it('returns 400 for invalid node payload', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}}
}); });
request(app) request(app)
.put('/nodes/node-red/foo') .put('/nodes/node-red/foo')
.send({}) .send({})
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -456,16 +452,14 @@ describe("nodes api", function() {
}); });
it('returns 400 for invalid module payload', function(done) { it('returns 400 for invalid module payload', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}}
}); });
request(app) request(app)
.put('/nodes/foo') .put('/nodes/foo')
.send({}) .send({})
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -476,11 +470,11 @@ describe("nodes api", function() {
}); });
it('returns 404 for unknown node', function(done) { it('returns 404 for unknown node', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}},
}); api:{
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo',function(id) { getNodeInfo: function() { return null }
return null; }
}); });
request(app) request(app)
@ -488,8 +482,6 @@ describe("nodes api", function() {
.send({enabled:false}) .send({enabled:false})
.expect(404) .expect(404)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getNodeInfo.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -498,11 +490,11 @@ describe("nodes api", function() {
}); });
it('returns 404 for unknown module', function(done) { it('returns 404 for unknown module', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}},
}); api:{
var getModuleInfo = sinon.stub(redNodes,'getModuleInfo',function(id) { getModuleInfo: function(id) { return null }
return null; }
}); });
request(app) request(app)
@ -510,8 +502,6 @@ describe("nodes api", function() {
.send({enabled:false}) .send({enabled:false})
.expect(404) .expect(404)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -520,24 +510,18 @@ describe("nodes api", function() {
}); });
it('enables disabled node', function(done) { it('enables disabled node', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}},
api:{
getNodeInfo: function() { return {id:"123",enabled: false} },
enableNode: function() { return when.resolve({id:"123",enabled: true,types:['a']}); }
}
}); });
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo',function(id) {
return {id:"123",enabled: false};
});
var enableNode = sinon.stub(redNodes,'enableNode',function(id) {
return when.resolve({id:"123",enabled: true,types:['a']});
});
request(app) request(app)
.put('/nodes/node-red/foo') .put('/nodes/node-red/foo')
.send({enabled:true}) .send({enabled:true})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getNodeInfo.restore();
enableNode.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -549,24 +533,18 @@ describe("nodes api", function() {
}); });
it('disables enabled node', function(done) { it('disables enabled node', function(done) {
var settingsAvailable = sinon.stub(settings,'available', function() { initNodes({
return true; settings:{available:function(){return true}},
api:{
getNodeInfo: function() { return {id:"123",enabled: true} },
disableNode: function() { return when.resolve({id:"123",enabled: false,types:['a']}); }
}
}); });
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo',function(id) {
return {id:"123",enabled: true};
});
var disableNode = sinon.stub(redNodes,'disableNode',function(id) {
return when.resolve({id:"123",enabled: false,types:['a']});
});
request(app) request(app)
.put('/nodes/node-red/foo') .put('/nodes/node-red/foo')
.send({enabled:false}) .send({enabled:false})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getNodeInfo.restore();
disableNode.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -579,31 +557,24 @@ describe("nodes api", function() {
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 enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) });
return true; var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) });
});
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo',function(id) {
return {id:"123",enabled: state};
});
var enableNode = sinon.stub(redNodes,'enableNode',function(id) {
return when.resolve({id:"123",enabled: true,types:['a']});
});
var disableNode = sinon.stub(redNodes,'disableNode',function(id) { initNodes({
return when.resolve({id:"123",enabled: false,types:['a']}); settings:{available:function(){return true}},
api:{
getNodeInfo: function() { return {id:"123",enabled: state} },
enableNode: enableNode,
disableNode: disableNode
}
}); });
request(app) request(app)
.put('/nodes/node-red/foo') .put('/nodes/node-red/foo')
.send({enabled:state}) .send({enabled:state})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getNodeInfo.restore();
var enableNodeCalled = enableNode.called; var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called; var disableNodeCalled = disableNode.called;
enableNode.restore();
disableNode.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -625,31 +596,24 @@ describe("nodes api", function() {
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 enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) });
return true; var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) });
});
var getNodeInfo = sinon.stub(redNodes,'getNodeInfo',function(id) {
return {id:"123",enabled: state, err:"foo" };
});
var enableNode = sinon.stub(redNodes,'enableNode',function(id) {
return when.resolve({id:"123",enabled: true,types:['a']});
});
var disableNode = sinon.stub(redNodes,'disableNode',function(id) { initNodes({
return when.resolve({id:"123",enabled: false,types:['a']}); settings:{available:function(){return true}},
api:{
getNodeInfo: function() { return {id:"123",enabled: state, err:"foo"} },
enableNode: enableNode,
disableNode: disableNode
}
}); });
request(app) request(app)
.put('/nodes/node-red/foo') .put('/nodes/node-red/foo')
.send({enabled:state}) .send({enabled:state})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getNodeInfo.restore();
var enableNodeCalled = enableNode.called; var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called; var disableNodeCalled = disableNode.called;
enableNode.restore();
disableNode.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -672,14 +636,7 @@ describe("nodes api", function() {
it('enables disabled module', function(done) { it('enables disabled module', function(done) {
var n1 = {id:"123",enabled:false,types:['a']}; var n1 = {id:"123",enabled:false,types:['a']};
var n2 = {id:"456",enabled:false,types:['b']}; var n2 = {id:"456",enabled:false,types:['b']};
var settingsAvailable = sinon.stub(settings,'available', function() { var enableNode = sinon.stub();
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() { enableNode.onFirstCall().returns((function() {
n1.enabled = true; n1.enabled = true;
return when.resolve(n1); return when.resolve(n1);
@ -689,15 +646,19 @@ describe("nodes api", function() {
return when.resolve(n2); return when.resolve(n2);
})()); })());
enableNode.returns(null); enableNode.returns(null);
initNodes({
settings:{available:function(){return true}},
api:{
getModuleInfo: function() { return {name:"node-red", nodes:[n1, n2]} },
enableNode: enableNode
}
});
request(app) request(app)
.put('/nodes/node-red') .put('/nodes/node-red')
.send({enabled:true}) .send({enabled:true})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
enableNode.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -713,14 +674,7 @@ describe("nodes api", function() {
it('disables enabled module', function(done) { it('disables enabled module', function(done) {
var n1 = {id:"123",enabled:true,types:['a']}; var n1 = {id:"123",enabled:true,types:['a']};
var n2 = {id:"456",enabled:true,types:['b']}; var n2 = {id:"456",enabled:true,types:['b']};
var settingsAvailable = sinon.stub(settings,'available', function() { var disableNode = sinon.stub();
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() { disableNode.onFirstCall().returns((function() {
n1.enabled = false; n1.enabled = false;
return when.resolve(n1); return when.resolve(n1);
@ -730,15 +684,19 @@ describe("nodes api", function() {
return when.resolve(n2); return when.resolve(n2);
})()); })());
disableNode.returns(null); disableNode.returns(null);
initNodes({
settings:{available:function(){return true}},
api:{
getModuleInfo: function() { return {name:"node-red", nodes:[n1, n2]} },
disableNode: disableNode
}
});
request(app) request(app)
.put('/nodes/node-red') .put('/nodes/node-red')
.send({enabled:false}) .send({enabled:false})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
disableNode.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -754,32 +712,30 @@ describe("nodes api", function() {
describe('no-ops if a node in module already in the right state', function() { describe('no-ops if a node in module already in the right state', function() {
function run(state,done) { function run(state,done) {
var node = {id:"123",enabled:state,types:['a']}; var node = {id:"123",enabled:state,types:['a']};
var settingsAvailable = sinon.stub(settings,'available', function() { var enableNode = sinon.spy(function(id) {
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; node.enabled = true;
return when.resolve(node); return when.resolve(node);
}); });
var disableNode = sinon.stub(redNodes,'disableNode',function(id) { var disableNode = sinon.spy(function(id) {
node.enabled = false; node.enabled = false;
return when.resolve(node); return when.resolve(node);
}); });
initNodes({
settings:{available:function(){return true}},
api:{
getModuleInfo: function() { return {name:"node-red", nodes:[node]}; },
enableNode: enableNode,
disableNode: disableNode
}
});
request(app) request(app)
.put('/nodes/node-red') .put('/nodes/node-red')
.send({enabled:state}) .send({enabled:state})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
var enableNodeCalled = enableNode.called; var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called; var disableNodeCalled = disableNode.called;
enableNode.restore();
disableNode.restore();
if (err) { if (err) {
throw err; throw err;
} }
@ -803,32 +759,31 @@ describe("nodes api", function() {
describe('does not no-op if err on a node in module', function() { describe('does not no-op if err on a node in module', function() {
function run(state,done) { function run(state,done) {
var node = {id:"123",enabled:state,types:['a'],err:"foo"}; var node = {id:"123",enabled:state,types:['a'],err:"foo"};
var settingsAvailable = sinon.stub(settings,'available', function() { var enableNode = sinon.spy(function(id) {
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; node.enabled = true;
return when.resolve(node); return when.resolve(node);
}); });
var disableNode = sinon.stub(redNodes,'disableNode',function(id) { var disableNode = sinon.spy(function(id) {
node.enabled = false; node.enabled = false;
return when.resolve(node); return when.resolve(node);
}); });
initNodes({
settings:{available:function(){return true}},
api:{
getModuleInfo: function() { return {name:"node-red", nodes:[node]}; },
enableNode: enableNode,
disableNode: disableNode
}
});
request(app) request(app)
.put('/nodes/node-red') .put('/nodes/node-red')
.send({enabled:state}) .send({enabled:state})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
settingsAvailable.restore();
getModuleInfo.restore();
var enableNodeCalled = enableNode.called; var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called; var disableNodeCalled = disableNode.called;
enableNode.restore();
disableNode.restore();
if (err) { if (err) {
throw err; throw err;
} }

View File

@ -31,13 +31,13 @@ describe("theme handler", function() {
sinon.stub(fs,"statSync",function() { return true; }); sinon.stub(fs,"statSync",function() { return true; });
}); });
afterEach(function() { afterEach(function() {
theme.init({}); theme.init({settings:{}});
fs.statSync.restore(); fs.statSync.restore();
}); });
it("applies the default theme", function() { it("applies the default theme", function() {
var result = theme.init({}); var result = theme.init({settings:{}});
should.not.exist(result); should.not.exist(result);
var context = theme.context(); var context = theme.context();
context.should.have.a.property("page"); context.should.have.a.property("page");
context.page.should.have.a.property("title","Node-RED"); context.page.should.have.a.property("title","Node-RED");
@ -45,12 +45,12 @@ describe("theme handler", function() {
context.should.have.a.property("header"); context.should.have.a.property("header");
context.header.should.have.a.property("title","Node-RED"); context.header.should.have.a.property("title","Node-RED");
context.header.should.have.a.property("image","red/images/node-red.png"); context.header.should.have.a.property("image","red/images/node-red.png");
should.not.exist(theme.settings()); should.not.exist(theme.settings());
}); });
it("picks up custom theme", function() { it("picks up custom theme", function() {
var result = theme.init({ var result = theme.init({settings:{
editorTheme: { editorTheme: {
page: { page: {
title: "Test Page Title", title: "Test Page Title",
@ -61,13 +61,13 @@ describe("theme handler", function() {
title: "Test Header Title", title: "Test Header Title",
image: "/absolute/path/to/header/image" // or null to remove image image: "/absolute/path/to/header/image" // or null to remove image
}, },
deployButton: { deployButton: {
type:"simple", type:"simple",
label:"Save", label:"Save",
icon: "/absolute/path/to/deploy/button/image" // or null to remove image icon: "/absolute/path/to/deploy/button/image" // or null to remove image
}, },
menu: { // Hide unwanted menu items by id. see editor/js/main.js:loadEditor for complete list menu: { // Hide unwanted menu items by id. see editor/js/main.js:loadEditor for complete list
"menu-item-import-library": false, "menu-item-import-library": false,
"menu-item-export-library": false, "menu-item-export-library": false,
@ -77,27 +77,27 @@ describe("theme handler", function() {
url: "http://example.com" url: "http://example.com"
} }
}, },
userMenu: false, // Hide the user-menu even if adminAuth is enabled userMenu: false, // Hide the user-menu even if adminAuth is enabled
login: { login: {
image: "/absolute/path/to/login/page/big/image" // a 256x256 image image: "/absolute/path/to/login/page/big/image" // a 256x256 image
} }
} }
}); }});
should.exist(result); should.exist(result);
var context = theme.context(); var context = theme.context();
context.should.have.a.property("page"); context.should.have.a.property("page");
context.page.should.have.a.property("title","Test Page Title"); context.page.should.have.a.property("title","Test Page Title");
context.should.have.a.property("header"); context.should.have.a.property("header");
context.header.should.have.a.property("title","Test Header Title"); context.header.should.have.a.property("title","Test Header Title");
var settings = theme.settings(); var settings = theme.settings();
settings.should.have.a.property("deployButton"); settings.should.have.a.property("deployButton");
settings.should.have.a.property("userMenu"); settings.should.have.a.property("userMenu");
settings.should.have.a.property("menu"); settings.should.have.a.property("menu");
}); });
}); });

View File

@ -20,14 +20,17 @@ var express = require("express");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var events = require("../../../red/events"); var EventEmitter = require('events').EventEmitter;
var events = new EventEmitter();
var ui = require("../../../red/api/ui"); var ui = require("../../../red/api/ui");
describe("ui api", function() { describe("ui api", function() {
var app; var app;
before(function() {
ui.init({events:events});
});
describe("slash handler", function() { describe("slash handler", function() {
before(function() { before(function() {
app = express(); app = express();

View File

@ -959,7 +959,7 @@ describe('Flow', function() {
done(); done();
}); });
}); });
it("moves any existing error object sideways",function(){ it("moves any existing error object sideways",function(done){
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},

View File

@ -145,7 +145,7 @@ describe("red/nodes/index", 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() { beforeEach(function() {
sinon.stub(registry,"getNodeInfo",function(id) { sinon.stub(registry,"getNodeInfo",function(id) {
if (id == "test") { if (id == "test") {
return {id:"1234",types:["test"]}; return {id:"1234",types:["test"]};
@ -159,7 +159,7 @@ describe("red/nodes/index", function() {
return randomNodeInfo; return randomNodeInfo;
}); });
}); });
after(function() { afterEach(function() {
registry.getNodeInfo.restore(); registry.getNodeInfo.restore();
registry.disableNode.restore(); registry.disableNode.restore();
}); });

View File

@ -20,18 +20,23 @@ var sinon = require("sinon");
var comms = require("../../red/comms"); var comms = require("../../red/comms");
var redNodes = require("../../red/nodes"); var redNodes = require("../../red/nodes");
var api = require("../../red/api"); var api = require("../../red/api");
var server = require("../../red/server"); var runtime = require("../../red/runtime");
var storage = require("../../red/storage"); var storage = require("../../red/storage");
var settings = require("../../red/settings"); var settings = require("../../red/settings");
var log = require("../../red/log"); var log = require("../../red/log");
describe("red/server", function() { describe("red/runtime", function() {
var commsMessages = []; var commsMessages = [];
var commsPublish; var commsPublish;
beforeEach(function() { beforeEach(function() {
commsMessages = []; commsMessages = [];
}); });
afterEach(function() {
if (console.log.restore) {
console.log.restore();
}
})
before(function() { before(function() {
commsPublish = sinon.stub(comms,"publish", function(topic,msg,retained) { commsPublish = sinon.stub(comms,"publish", function(topic,msg,retained) {
@ -42,26 +47,48 @@ describe("red/server", function() {
commsPublish.restore(); commsPublish.restore();
}); });
it("initialises components", function() { describe("init", function() {
var commsInit = sinon.stub(comms,"init",function() {}); var commsInit;
var dummyServer = {}; var apiInit;
server.init(dummyServer,{testSettings: true, httpAdminRoot:"/", load:function() { return when.resolve();}}); beforeEach(function() {
commsInit = sinon.stub(comms,"init",function() {});
apiInit = sinon.stub(api,"init",function() {});
});
afterEach(function() {
commsInit.restore();
apiInit.restore();
})
commsInit.called.should.be.true; it("initialises components", function() {
var dummyServer = {};
runtime.init(dummyServer,{testSettings: true, httpAdminRoot:"/", load:function() { return when.resolve();}});
commsInit.called.should.be.true;
apiInit.called.should.be.true;
should.exist(server.app); should.exist(runtime.app);
should.exist(server.nodeApp); should.exist(runtime.nodeApp);
server.server.should.equal(dummyServer); runtime.server.should.equal(dummyServer);
});
commsInit.restore(); it("doesn't init api if httpAdminRoot set to false",function() {
var dummyServer = {};
runtime.init(dummyServer,{testSettings: true, httpAdminRoot:false, load:function() { return when.resolve();}});
commsInit.called.should.be.true;
apiInit.called.should.be.false;
should.exist(runtime.app);
should.exist(runtime.nodeApp);
runtime.server.should.equal(dummyServer);
});
}); });
describe("start",function() { describe("start",function() {
var commsInit; var commsInit;
var storageInit; var storageInit;
var settingsLoad; var settingsLoad;
var apiInit;
var logMetric; var logMetric;
var logWarn; var logWarn;
var logInfo; var logInfo;
@ -77,7 +104,6 @@ describe("red/server", function() {
beforeEach(function() { beforeEach(function() {
commsInit = sinon.stub(comms,"init",function() {}); commsInit = sinon.stub(comms,"init",function() {});
storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();}); storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();});
apiInit = sinon.stub(api,"init",function() {});
logMetric = sinon.stub(log,"metric",function() { return false; }); logMetric = sinon.stub(log,"metric",function() { return false; });
logWarn = sinon.stub(log,"warn",function() { }); logWarn = sinon.stub(log,"warn",function() { });
logInfo = sinon.stub(log,"info",function() { }); logInfo = sinon.stub(log,"info",function() { });
@ -92,7 +118,6 @@ describe("red/server", function() {
afterEach(function() { afterEach(function() {
commsInit.restore(); commsInit.restore();
storageInit.restore(); storageInit.restore();
apiInit.restore();
logMetric.restore(); logMetric.restore();
logWarn.restore(); logWarn.restore();
logInfo.restore(); logInfo.restore();
@ -112,10 +137,11 @@ describe("red/server", function() {
{ module:"module",enabled:true,loaded:false,types:["typeA","typeB"]} // missing { module:"module",enabled:true,loaded:false,types:["typeA","typeB"]} // missing
].filter(cb); ].filter(cb);
}); });
server.init({},{testSettings: true, httpAdminRoot:"/", load:function() { return when.resolve();}}); runtime.init({},{testSettings: true, httpAdminRoot:"/", load:function() { return when.resolve();}});
server.start().then(function() { sinon.stub(console,"log");
runtime.start().then(function() {
console.log.restore();
try { try {
apiInit.calledOnce.should.be.true;
storageInit.calledOnce.should.be.true; storageInit.calledOnce.should.be.true;
redNodesInit.calledOnce.should.be.true; redNodesInit.calledOnce.should.be.true;
redNodesLoad.calledOnce.should.be.true; redNodesLoad.calledOnce.should.be.true;
@ -142,10 +168,11 @@ describe("red/server", function() {
].filter(cb); ].filter(cb);
}); });
var serverInstallModule = sinon.stub(redNodes,"installModule",function(name) { return when.resolve();}); var serverInstallModule = sinon.stub(redNodes,"installModule",function(name) { return when.resolve();});
server.init({},{testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return when.resolve();}}); runtime.init({},{testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return when.resolve();}});
server.start().then(function() { sinon.stub(console,"log");
runtime.start().then(function() {
console.log.restore();
try { try {
apiInit.calledOnce.should.be.true;
logWarn.calledWithMatch("Failed to register 2 node types"); logWarn.calledWithMatch("Failed to register 2 node types");
logWarn.calledWithMatch("Missing node modules"); logWarn.calledWithMatch("Missing node modules");
logWarn.calledWithMatch(" - module: typeA, typeB"); logWarn.calledWithMatch(" - module: typeA, typeB");
@ -167,11 +194,11 @@ describe("red/server", function() {
{ err:"errored",name:"errName" } // error { err:"errored",name:"errName" } // error
].filter(cb); ].filter(cb);
}); });
server.init({},{testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return when.resolve();}}); runtime.init({},{testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return when.resolve();}});
server.start().then(function() { sinon.stub(console,"log");
runtime.start().then(function() {
console.log.restore();
try { try {
apiInit.calledOnce.should.be.true;
logWarn.neverCalledWithMatch("Failed to register 1 node type"); logWarn.neverCalledWithMatch("Failed to register 1 node type");
logWarn.calledWithMatch("[errName] errored"); logWarn.calledWithMatch("[errName] errored");
done(); done();
@ -187,11 +214,12 @@ describe("red/server", function() {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []}); redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
logMetric.restore(); logMetric.restore();
logMetric = sinon.stub(log,"metric",function() { return true; }); logMetric = sinon.stub(log,"metric",function() { return true; });
server.init({},{testSettings: true, runtimeMetricInterval:400, httpAdminRoot:"/", load:function() { return when.resolve();}}); runtime.init({},{testSettings: true, runtimeMetricInterval:200, httpAdminRoot:"/", load:function() { return when.resolve();}});
server.start().then(function() { sinon.stub(console,"log");
runtime.start().then(function() {
console.log.restore();
setTimeout(function() { setTimeout(function() {
try { try {
apiInit.calledOnce.should.be.true;
logLog.args.should.have.lengthOf(3); logLog.args.should.have.lengthOf(3);
logLog.args[0][0].should.have.property("level",log.METRIC); logLog.args[0][0].should.have.property("level",log.METRIC);
logLog.args[0][0].should.have.property("event","runtime.memory.rss"); logLog.args[0][0].should.have.property("event","runtime.memory.rss");
@ -203,35 +231,22 @@ describe("red/server", function() {
} catch(err) { } catch(err) {
done(err); done(err);
} finally { } finally {
server.stop(); runtime.stop();
commsStop.restore(); commsStop.restore();
stopFlows.restore(); stopFlows.restore();
} }
},500); },300);
}); });
}); });
it("doesn't init api if httpAdminRoot set to false",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
server.init({},{testSettings: true, httpAdminRoot:false, load:function() { return when.resolve();}});
server.start().then(function() {
setTimeout(function() {
try {
apiInit.calledOnce.should.be.false;
done();
} catch(err) {
done(err);
}
},500);
});
});
}); });
it("stops components", function() { it("stops components", function() {
var commsStop = sinon.stub(comms,"stop",function() {} ); var commsStop = sinon.stub(comms,"stop",function() {} );
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} );
server.stop(); runtime.stop();
commsStop.called.should.be.true; commsStop.called.should.be.true;
stopFlows.called.should.be.true; stopFlows.called.should.be.true;

View File

@ -20,11 +20,11 @@ var settings = require("../../red/settings");
describe("red/settings", function() { describe("red/settings", function() {
afterEach(function() { afterEach(function() {
settings.reset(); settings.reset();
}); });
it('wraps the user settings as read-only properties', function() { it('wraps the user settings as read-only properties', function() {
var userSettings = { var userSettings = {
a: 123, a: 123,
@ -32,22 +32,22 @@ describe("red/settings", function() {
c: [1,2,3] c: [1,2,3]
} }
settings.init(userSettings); settings.init(userSettings);
settings.available().should.be.false; settings.available().should.be.false;
settings.a.should.equal(123); settings.a.should.equal(123);
settings.b.should.equal("test"); settings.b.should.equal("test");
settings.c.should.be.an.Array.with.lengthOf(3); settings.c.should.be.an.Array.with.lengthOf(3);
settings.get("a").should.equal(123); settings.get("a").should.equal(123);
settings.get("b").should.equal("test"); settings.get("b").should.equal("test");
settings.get("c").should.be.an.Array.with.lengthOf(3); settings.get("c").should.be.an.Array.with.lengthOf(3);
/*jshint immed: false */ /*jshint immed: false */
(function() { (function() {
settings.a = 456; settings.a = 456;
}).should.throw(); }).should.throw();
settings.c.push(5); settings.c.push(5);
settings.c.should.be.an.Array.with.lengthOf(4); settings.c.should.be.an.Array.with.lengthOf(4);
@ -55,7 +55,7 @@ describe("red/settings", function() {
(function() { (function() {
settings.set("a",456); settings.set("a",456);
}).should.throw(); }).should.throw();
/*jshint immed: false */ /*jshint immed: false */
(function() { (function() {
settings.set("a",456); settings.set("a",456);
@ -70,9 +70,9 @@ describe("red/settings", function() {
(function() { (function() {
settings.set("unknown",456); settings.set("unknown",456);
}).should.throw(); }).should.throw();
}); });
it('loads global settings from storage', function(done) { it('loads global settings from storage', function(done) {
var userSettings = { var userSettings = {
a: 123, a: 123,
@ -94,7 +94,7 @@ describe("red/settings", function() {
settings.init(userSettings); settings.init(userSettings);
settings.available().should.be.false; settings.available().should.be.false;
/*jshint immed: false */ /*jshint immed: false */
(function() { (function() {
settings.get("unknown"); settings.get("unknown");
@ -117,7 +117,7 @@ describe("red/settings", function() {
done(err); done(err);
}); });
}); });
it('removes persistent settings when reset', function() { it('removes persistent settings when reset', function() {
var userSettings = { var userSettings = {
a: 123, a: 123,
@ -125,18 +125,18 @@ describe("red/settings", function() {
c: [1,2,3] c: [1,2,3]
} }
settings.init(userSettings); settings.init(userSettings);
settings.available().should.be.false; settings.available().should.be.false;
settings.should.have.property("a",123); settings.should.have.property("a",123);
settings.should.have.property("b","test"); settings.should.have.property("b","test");
settings.c.should.be.an.Array.with.lengthOf(3); settings.c.should.be.an.Array.with.lengthOf(3);
settings.reset(); settings.reset();
settings.should.not.have.property("a"); settings.should.not.have.property("a");
settings.should.not.have.property("d"); settings.should.not.have.property("d");
settings.should.not.have.property("c"); settings.should.not.have.property("c");
}); });
}); });