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

Fixup all the tests

This commit is contained in:
Nick O'Leary 2018-04-24 15:01:49 +01:00
parent 34832d5942
commit 5d064aa1d7
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
50 changed files with 3480 additions and 1762 deletions

View File

@ -15,6 +15,7 @@
**/ **/
var runtimeAPI; var runtimeAPI;
var apiUtils = require("../util");
module.exports = { module.exports = {
init: function(_runtimeAPI) { init: function(_runtimeAPI) {
@ -23,7 +24,7 @@ module.exports = {
get: function(req,res) { get: function(req,res) {
var version = req.get("Node-RED-API-Version")||"v1"; var version = req.get("Node-RED-API-Version")||"v1";
if (!/^v[12]$/.test(version)) { if (!/^v[12]$/.test(version)) {
return res.status(500).json({code:"invalid_api_version", message:"Invalid API Version requested"}); return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
} }
var opts = { var opts = {
user: req.user user: req.user
@ -41,7 +42,7 @@ module.exports = {
post: function(req,res) { post: function(req,res) {
var version = req.get("Node-RED-API-Version")||"v1"; var version = req.get("Node-RED-API-Version")||"v1";
if (!/^v[12]$/.test(version)) { if (!/^v[12]$/.test(version)) {
return res.status(500).json({code:"invalid_api_version", message:"Invalid API Version requested"}); return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
} }
var opts = { var opts = {
user: req.user, user: req.user,

View File

@ -56,7 +56,7 @@ module.exports = {
user: req.user, user: req.user,
module: req.params[0] module: req.params[0]
} }
runtimeAPI.nodes.removeModule(opts).then(function(info) { runtimeAPI.nodes.removeModule(opts).then(function() {
res.status(204).end(); res.status(204).end();
}).catch(function(err) { }).catch(function(err) {
apiUtils.rejectHandler(req,res,err); apiUtils.rejectHandler(req,res,err);
@ -77,7 +77,7 @@ module.exports = {
} else { } else {
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
runtimeAPI.nodes.getNodeConfig(opts).then(function(result) { runtimeAPI.nodes.getNodeConfig(opts).then(function(result) {
return res.json(result); return res.send(result);
}).catch(function(err) { }).catch(function(err) {
apiUtils.rejectHandler(req,res,err); apiUtils.rejectHandler(req,res,err);
}) })
@ -100,7 +100,7 @@ module.exports = {
var body = req.body; var body = req.body;
if (!body.hasOwnProperty("enabled")) { if (!body.hasOwnProperty("enabled")) {
// log.audit({event: "nodes.module.set",error:"invalid_request"},req); // log.audit({event: "nodes.module.set",error:"invalid_request"},req);
res.status(400).json({error:"invalid_request", message:"Invalid request"}); res.status(400).json({code:"invalid_request", message:"Invalid request"});
return; return;
} }
var opts = { var opts = {
@ -119,7 +119,7 @@ module.exports = {
var body = req.body; var body = req.body;
if (!body.hasOwnProperty("enabled")) { if (!body.hasOwnProperty("enabled")) {
// log.audit({event: "nodes.module.set",error:"invalid_request"},req); // log.audit({event: "nodes.module.set",error:"invalid_request"},req);
res.status(400).json({error:"invalid_request", message:"Invalid request"}); res.status(400).json({code:"invalid_request", message:"Invalid request"});
return; return;
} }
var opts = { var opts = {

View File

@ -28,6 +28,8 @@ var runtimeAPI;
var wsServer; var wsServer;
var activeConnections = []; var activeConnections = [];
var anonymousUser;
var retained = {}; var retained = {};
var heartbeatTimer; var heartbeatTimer;
@ -173,7 +175,8 @@ CommsConnection.prototype.subscribe = function(topic) {
function start() { function start() {
if (!settings.disableEditor) { if (!settings.disableEditor) {
Users.default().then(function(anonymousUser) { Users.default().then(function(_anonymousUser) {
anonymousUser = _anonymousUser;
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000; var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
var path = settings.httpAdminRoot || "/"; var path = settings.httpAdminRoot || "/";
path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms"; path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";

View File

@ -89,7 +89,6 @@ module.exports = {
// Library // Library
var library = require("./library"); var library = require("./library");
library.init(runtimeAPI); library.init(runtimeAPI);
editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler); editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler);
editorApp.get(/library\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry); editorApp.get(/library\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
editorApp.post(/library\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry); editorApp.post(/library\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
@ -107,7 +106,7 @@ module.exports = {
// User Settings // User Settings
editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler); editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler);
// SSH keys // SSH keys
editorApp.use("/settings/user/keys",info.sshkeys()); editorApp.use("/settings/user/keys",needsPermission("settings.write"),info.sshkeys());
return editorApp; return editorApp;
} }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
**/ **/
var apiUtils = require("../util");
var express = require("express"); var express = require("express");
var runtimeAPI; var runtimeAPI;
var needsPermission = require("../auth").needsPermission;
function getUsername(userObj) { function getUsername(userObj) {
var username = '__default'; var username = '__default';
@ -34,7 +34,7 @@ module.exports = {
var app = express(); var app = express();
// List all SSH keys // List all SSH keys
app.get("/", needsPermission("settings.read"), function(req,res) { app.get("/", function(req,res) {
var opts = { var opts = {
user: req.user user: req.user
} }
@ -48,7 +48,7 @@ module.exports = {
}); });
// Get SSH key detail // Get SSH key detail
app.get("/:id", needsPermission("settings.read"), function(req,res) { app.get("/:id", function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id
@ -63,11 +63,17 @@ module.exports = {
}); });
// Generate a SSH key // Generate a SSH key
app.post("/", needsPermission("settings.write"), function(req,res) { app.post("/", function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id
} }
// TODO: validate params
opts.name = req.body.name;
opts.password = req.body.password;
opts.comment = req.body.comment;
opts.size = req.body.size;
runtimeAPI.settings.generateUserKey(opts).then(function(name) { runtimeAPI.settings.generateUserKey(opts).then(function(name) {
res.json({ res.json({
name: name name: name
@ -78,12 +84,12 @@ module.exports = {
}); });
// Delete a SSH key // Delete a SSH key
app.delete("/:id", needsPermission("settings.write"), function(req,res) { app.delete("/:id", function(req,res) {
var opts = { var opts = {
user: req.user, user: req.user,
id: req.params.id id: req.params.id
} }
runtimeAPI.settings.generateUserKey(opts).then(function(name) { runtimeAPI.settings.removeUserKey(opts).then(function(name) {
res.status(204).end(); res.status(204).end();
}).catch(function(err) { }).catch(function(err) {
apiUtils.rejectHandler(req,res,err); apiUtils.rejectHandler(req,res,err);

View File

@ -89,23 +89,9 @@ module.exports = {
init: init, init: init,
start: start, start: start,
stop: stop, stop: stop,
library: {
register: function(type) {
if (editor) {
editor.registerLibrary(type);
}
}
},
auth: { auth: {
needsPermission: auth.needsPermission needsPermission: auth.needsPermission
}, },
comms: {
publish: function(topic,data,retain) {
if (editor) {
editor.publish(topic,data,retain);
}
}
},
get adminApp() { return adminApp; }, get adminApp() { return adminApp; },
get server() { return server; } get server() { return server; }
}; };

View File

@ -21,7 +21,6 @@ var i18n = require("../util").i18n; // TODO: separate module
module.exports = { module.exports = {
errorHandler: function(err,req,res,next) { errorHandler: function(err,req,res,next) {
console.error(err.stack);
if (err.message === "request entity too large") { if (err.message === "request entity too large") {
log.error(err); log.error(err);
} else { } else {
@ -40,13 +39,9 @@ module.exports = {
return lang; return lang;
}, },
rejectHandler: function(req,res,err) { rejectHandler: function(req,res,err) {
res.status(err.status||500); res.status(err.status||500).json({
if (err.code || err.message) {
res.json({
code: err.code||"unexpected_error", code: err.code||"unexpected_error",
message: err.message message: err.message||err.toString()
}) });
}
res.end();
} }
} }

View File

@ -111,9 +111,20 @@ module.exports = {
util: runtime.util, util: runtime.util,
version: runtime.version, version: runtime.version,
events: runtime.events, events: runtime.events,
comms: {
comms: api.comms, publish: function(topic,data,retain) {
library: api.library, runtime.events.emit("comms",{
topic: topic,
data: data,
retain: retain
})
}
},
library: {
register: function(type) {
return runtime.library.register(null,type);
}
},
auth: api.auth, auth: api.auth,
get app() { console.log("Deprecated use of RED.app - use RED.httpAdmin instead"); return runtime.app }, get app() { console.log("Deprecated use of RED.app - use RED.httpAdmin instead"); return runtime.app },

View File

@ -49,13 +49,15 @@ function publish(topic,data,retain) {
} else { } else {
delete retained[topic]; delete retained[topic];
} }
connections.forEach(connection => connection.send(topic,data,retain)) connections.forEach(connection => connection.send(topic,data))
} }
var api = module.exports = { var api = module.exports = {
init: function(_runtime) { init: function(_runtime) {
runtime = _runtime; runtime = _runtime;
connections = [];
retained = {};
runtime.events.removeListener("node-status",handleStatusEvent); runtime.events.removeListener("node-status",handleStatusEvent);
runtime.events.on("node-status",handleStatusEvent); runtime.events.on("node-status",handleStatusEvent);
runtime.events.removeListener("runtime-event",handleRuntimeEvent); runtime.events.removeListener("runtime-event",handleRuntimeEvent);

View File

@ -135,6 +135,7 @@ var api = module.exports = {
} else { } else {
runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"}); runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"});
var err = new Error(); var err = new Error();
err.code = "not_found";
err.status = 404; err.status = 404;
return reject(err); return reject(err);
} }

View File

@ -42,9 +42,6 @@ var stubbedExpressApp = {
delete: function() {} delete: function() {}
} }
var adminApi = { var adminApi = {
library: {
register: function() {}
},
auth: { auth: {
needsPermission: function() {} needsPermission: function() {}
}, },

View File

@ -29,17 +29,20 @@ function init(_runtime) {
} }
function registerType(id,type) { function registerType(id,type) {
if (knownTypes.hasOwnProperty(type)) { // TODO: would like to enforce this, but currently the tests register the same type multiple
throw new Error(`Library type '${type}' already registerd by ${id}'`) // times and have no way to remove themselves.
} // if (knownTypes.hasOwnProperty(type)) {
// throw new Error(`Library type '${type}' already registered by ${id}'`)
// }
knownTypes[type] = id; knownTypes[type] = id;
} }
function getAllEntries(type) { // function getAllEntries(type) {
if (!knownTypes.hasOwnProperty(type)) { // if (!knownTypes.hasOwnProperty(type)) {
throw new Error(`Unknown library type '${type}'`); // throw new Error(`Unknown library type '${type}'`);
} // }
} // }
function getEntry(type,path) { function getEntry(type,path) {
if (type !== 'flows') { if (type !== 'flows') {
if (!knownTypes.hasOwnProperty(type)) { if (!knownTypes.hasOwnProperty(type)) {
@ -67,12 +70,11 @@ function getEntry(type,path) {
return reject(err); return reject(err);
} }
} }
} else { }
// IF we get here, we didn't find the file // IF we get here, we didn't find the file
var error = new Error("not_found"); var error = new Error("not_found");
error.code = "not_found"; error.code = "not_found";
return reject(error); return reject(error);
}
} else { } else {
resolve(storage.getFlow(path)); resolve(storage.getFlow(path));
} }
@ -92,8 +94,8 @@ function saveEntry(type,path,meta,body) {
module.exports = { module.exports = {
init: init, init: init,
registerType: registerType, register: registerType,
getAllEntries: getAllEntries, // getAllEntries: getAllEntries,
getEntry: getEntry, getEntry: getEntry,
saveEntry: saveEntry saveEntry: saveEntry

View File

@ -79,6 +79,11 @@ function createNodeApi(node) {
retain: retain retain: retain
}) })
} }
},
library: {
register: function(type) {
return runtime.library.register(node.id,type);
}
} }
} }
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials" ]); copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials" ]);
@ -88,11 +93,6 @@ function createNodeApi(node) {
copyObjectProperties(runtime.log,red.log,null,["init"]); copyObjectProperties(runtime.log,red.log,null,["init"]);
copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]); copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]);
if (runtime.adminApi) { if (runtime.adminApi) {
red.library = {
register: function(type) {
return runtime.library.registerType(node.id,type);
}
};
red.auth = runtime.adminApi.auth; red.auth = runtime.adminApi.auth;
red.httpAdmin = runtime.adminApi.adminApp; red.httpAdmin = runtime.adminApi.adminApp;
red.httpNode = runtime.nodeApp; red.httpNode = runtime.nodeApp;
@ -100,12 +100,6 @@ function createNodeApi(node) {
} else { } else {
//TODO: runtime.adminApi is always stubbed if not enabled, so this block //TODO: runtime.adminApi is always stubbed if not enabled, so this block
// is unused - but may be needed for the unit tests // is unused - but may be needed for the unit tests
red.comms = {
publish: function() {}
};
red.library = {
register: function() {}
};
red.auth = { red.auth = {
needsPermission: function() {} needsPermission: function() {}
}; };

View File

@ -35,7 +35,7 @@ var redNodes = require("../../red/runtime/nodes");
var flows = require("../../red/runtime/nodes/flows"); var flows = require("../../red/runtime/nodes/flows");
var credentials = require("../../red/runtime/nodes/credentials"); var credentials = require("../../red/runtime/nodes/credentials");
var comms = require("../../red/api/editor/comms.js"); var comms = require("../../red/api/editor/comms.js");
var log = require("../../red/runtime/log.js"); var log = require("../../red/util/log.js");
var context = require("../../red/runtime/nodes/context.js"); var context = require("../../red/runtime/nodes/context.js");
var events = require("../../red/runtime/events.js"); var events = require("../../red/runtime/events.js");

View File

@ -38,18 +38,23 @@ describe("api/admin/flow", function() {
describe("get", function() { describe("get", function() {
before(function() { before(function() {
var opts;
flow.init({ flow.init({
settings:{}, flows: {
nodes: { getFlow: function(_opts) {
getFlow: function(id) { opts = _opts;
if (id === '123') { if (opts.id === '123') {
return {id:'123'} return Promise.resolve({id:'123'});
} else { } else {
return null; var err = new Error("message");
err.code = "not_found";
err.status = 404;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
} }
} }
},
log:{ audit: sinon.stub() }
}); });
}) })
it('gets a known flow', function(done) { it('gets a known flow', function(done) {
@ -75,19 +80,24 @@ describe("api/admin/flow", function() {
}); });
describe("add", function() { describe("add", function() {
var opts;
before(function() { before(function() {
flow.init({ flow.init({
settings:{}, flows: {
nodes: { addFlow: function(_opts) {
addFlow: function(f) { opts = _opts;
if (f.id === "123") { if (opts.flow.id === "123") {
return when.resolve('123') return Promise.resolve('123')
} else { } else {
return when.reject(new Error("test error")); var err = new Error("random error");
err.code = "random_error";
err.status = 400;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
} }
} }
},
log:{ audit: sinon.stub() }
}); });
}) })
it('adds a new flow', function(done) { it('adds a new flow', function(done) {
@ -114,8 +124,8 @@ describe("api/admin/flow", function() {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.has.a.property('error','unexpected_error'); res.body.should.has.a.property('code','random_error');
res.body.should.has.a.property('message','Error: test error'); res.body.should.has.a.property('message','random error');
done(); done();
}); });
@ -123,35 +133,29 @@ describe("api/admin/flow", function() {
}) })
describe("update", function() { describe("update", function() {
var nodes;
var opts;
before(function() { before(function() {
nodes = {
updateFlow: function(id,f) {
var err;
if (id === "123") {
return when.resolve()
} else if (id === "unknown") {
err = new Error();
err.code = 404;
throw err;
} else if (id === "unexpected") {
err = new Error();
err.code = 500;
throw err;
} else {
return when.reject(new Error("test error"));
}
}
};
flow.init({ flow.init({
settings:{}, flows: {
nodes: nodes, updateFlow: function(_opts) {
log:{ audit: sinon.stub() } opts = _opts;
if (opts.id === "123") {
return Promise.resolve('123')
} else {
var err = new Error("random error");
err.code = "random_error";
err.status = 400;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
}
}); });
}) })
it('updates an existing flow', function(done) { it('updates an existing flow', function(done) {
sinon.spy(nodes,"updateFlow");
request(app) request(app)
.put('/flow/123') .put('/flow/123')
.set('Accept', 'application/json') .set('Accept', 'application/json')
@ -162,115 +166,79 @@ describe("api/admin/flow", function() {
return done(err); return done(err);
} }
res.body.should.has.a.property('id','123'); res.body.should.has.a.property('id','123');
nodes.updateFlow.calledOnce.should.be.true(); opts.should.have.property('id','123');
nodes.updateFlow.lastCall.args[0].should.eql('123'); opts.should.have.property('flow',{id:'123'})
nodes.updateFlow.lastCall.args[1].should.eql({id:'123'});
nodes.updateFlow.restore();
done(); done();
}); });
}) })
it('404s on an unknown flow', function(done) { it('400 an invalid flow', function(done) {
request(app) request(app)
.put('/flow/unknown') .put('/flow/456')
.set('Accept', 'application/json') .set('Accept', 'application/json')
.send({id:'123'}) .send({id:'456'})
.expect(404)
.end(done);
})
it('400 on async update error', function(done) {
request(app)
.put('/flow/async_error')
.set('Accept', 'application/json')
.send({id:'123'})
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.has.a.property('error','unexpected_error'); res.body.should.has.a.property('code','random_error');
res.body.should.has.a.property('message','Error: test error'); res.body.should.has.a.property('message','random error');
done();
});
})
it('400 on sync update error', function(done) {
request(app)
.put('/flow/unexpected')
.set('Accept', 'application/json')
.send({id:'123'})
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.has.a.property('error',500);
res.body.should.has.a.property('message','Error');
done(); done();
}); });
}) })
}) })
describe("delete", function() { describe("delete", function() {
var nodes;
var opts;
before(function() { before(function() {
nodes = {
removeFlow: function(id) {
var err;
if (id === "123") {
return when.resolve()
} else if (id === "unknown") {
err = new Error();
err.code = 404;
throw err;
} else if (id === "unexpected") {
err = new Error();
err.code = 500;
throw err;
}
}
};
flow.init({ flow.init({
settings:{}, flows: {
nodes: nodes, deleteFlow: function(_opts) {
log:{ audit: sinon.stub() } opts = _opts;
if (opts.id === "123") {
return Promise.resolve()
} else {
var err = new Error("random error");
err.code = "random_error";
err.status = 400;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
}
}); });
}) })
it('updates an existing flow', function(done) { it('deletes an existing flow', function(done) {
sinon.spy(nodes,"removeFlow");
request(app) request(app)
.delete('/flow/123') .del('/flow/123')
.set('Accept', 'application/json')
.expect(204) .expect(204)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
nodes.removeFlow.calledOnce.should.be.true(); opts.should.have.property('id','123');
nodes.removeFlow.lastCall.args[0].should.eql('123');
nodes.removeFlow.restore();
done(); done();
}); });
}) })
it('404s on an unknown flow', function(done) { it('400 an invalid flow', function(done) {
request(app) request(app)
.delete('/flow/unknown') .del('/flow/456')
.expect(404) .set('Accept', 'application/json')
.end(done);
})
it('400 on remove error', function(done) {
request(app)
.delete('/flow/unexpected')
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.has.a.property('error',500); res.body.should.has.a.property('code','random_error');
res.body.should.has.a.property('message','Error'); res.body.should.has.a.property('message','random error');
done(); done();
}); });
}) })

View File

@ -19,7 +19,6 @@ var request = require('supertest');
var express = require('express'); var express = require('express');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var sinon = require('sinon'); var sinon = require('sinon');
var when = require('when');
var flows = require("../../../../red/api/admin/flows"); var flows = require("../../../../red/api/admin/flows");
@ -36,10 +35,8 @@ describe("api/admin/flows", function() {
it('returns flow - v1', function(done) { it('returns flow - v1', function(done) {
flows.init({ flows.init({
settings: {}, flows:{
log:{warn:function(){},_:function(){},audit:function(){}}, getFlows: function() { return Promise.resolve({rev:"123",flows:[1,2,3]}); }
nodes:{
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
} }
}); });
request(app) request(app)
@ -60,10 +57,8 @@ describe("api/admin/flows", function() {
}); });
it('returns flow - v2', function(done) { it('returns flow - v2', function(done) {
flows.init({ flows.init({
settings: {}, flows:{
log:{warn:function(){},_:function(){},audit:function(){}}, getFlows: function() { return Promise.resolve({rev:"123",flows:[1,2,3]}); }
nodes:{
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
} }
}); });
request(app) request(app)
@ -104,10 +99,9 @@ describe("api/admin/flows", function() {
}); });
}); });
it('sets flows - default - v1', function(done) { it('sets flows - default - v1', function(done) {
var setFlows = sinon.spy(function() { return when.resolve();}); var setFlows = sinon.spy(function() { return Promise.resolve();});
flows.init({ flows.init({
log:{warn:function(){},_:function(){},audit:function(){}}, flows:{
nodes:{
setFlows: setFlows setFlows: setFlows
} }
}); });
@ -120,15 +114,14 @@ describe("api/admin/flows", function() {
return done(err); return done(err);
} }
setFlows.calledOnce.should.be.true(); setFlows.calledOnce.should.be.true();
setFlows.lastCall.args[1].should.eql('full'); setFlows.lastCall.args[0].should.have.property('deploymentType','full');
done(); done();
}); });
}); });
it('sets flows - non-default - v1', function(done) { it('sets flows - non-default - v1', function(done) {
var setFlows = sinon.spy(function() { return when.resolve();}); var setFlows = sinon.spy(function() { return Promise.resolve();});
flows.init({ flows.init({
log:{warn:function(){},_:function(){},audit:function(){}}, flows:{
nodes:{
setFlows: setFlows setFlows: setFlows
} }
}); });
@ -142,19 +135,22 @@ describe("api/admin/flows", function() {
return done(err); return done(err);
} }
setFlows.calledOnce.should.be.true(); setFlows.calledOnce.should.be.true();
setFlows.lastCall.args[1].should.eql('nodes'); setFlows.lastCall.args[0].should.have.property('deploymentType','nodes');
done(); done();
}); });
}); });
it('set flows - rejects mismatched revision - v2', function(done) { it('set flows - rejects mismatched revision - v2', function(done) {
var setFlows = sinon.spy(function() { return when.resolve();});
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
flows.init({ flows.init({
log:{warn:function(){},_:function(){},audit:function(){}}, flows:{
nodes:{ setFlows: function() {
setFlows: setFlows, var err = new Error("mismatch");
getFlows: getFlows err.code = "version_mismatch";
err.status = 409;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
} }
}); });
request(app) request(app)
@ -171,54 +167,6 @@ describe("api/admin/flows", function() {
done(); done();
}); });
}); });
it('set flows - rev provided - v2', function(done) {
var setFlows = sinon.spy(function() { return when.resolve(456);});
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows,
getFlows: getFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.send({rev:123,flows:[4,5,6]})
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("rev",456);
done();
});
});
it('set flows - no rev provided - v2', function(done) {
var setFlows = sinon.spy(function() { return when.resolve(456);});
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows,
getFlows: getFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.send({flows:[4,5,6]})
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("rev",456);
done();
});
});
it('sets flow - bad version', function(done) { it('sets flow - bad version', function(done) {
request(app) request(app)
.post('/flows') .post('/flows')
@ -238,11 +186,10 @@ describe("api/admin/flows", function() {
}); });
}); });
it('reloads flows', function(done) { it('reloads flows', function(done) {
var loadFlows = sinon.spy(function() { return when.resolve(); }); var setFlows = sinon.spy(function() { return Promise.resolve();});
flows.init({ flows.init({
log:{warn:function(){},_:function(){},audit:function(){}}, flows:{
nodes:{ setFlows: setFlows
loadFlows: loadFlows
} }
}); });
request(app) request(app)
@ -254,29 +201,9 @@ describe("api/admin/flows", function() {
if (err) { if (err) {
return done(err); return done(err);
} }
loadFlows.called.should.be.true(); setFlows.called.should.be.true();
setFlows.lastCall.args[0].should.not.have.property('flows');
done(); done();
}); });
}); });
it('returns error when set fails', function(done) {
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: function() { return when.reject(new Error("expected error")); }
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.expect(500)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("message","expected error");
done();
});
});
}); });

View File

@ -27,31 +27,17 @@ var apiUtil = require("../../../../red/api/util");
describe("api/admin/nodes", function() { describe("api/admin/nodes", function() {
var app; var app;
function initNodes(runtime) {
runtime.log = {
audit:function(e){},//console.log(e)},
_:function(){},
info: function(){},
warn: function(){}
}
runtime.events = {
emit: function(){}
}
nodes.init(runtime);
}
before(function() { before(function() {
app = express(); app = express();
app.use(bodyParser.json()); app.use(bodyParser.json());
app.get("/nodes",nodes.getAll); app.get("/nodes",nodes.getAll);
app.post("/nodes",nodes.post); app.post("/nodes",nodes.post);
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.getModule); app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.getModule);
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.getSet);
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.putModule); app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.putModule);
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.getSet);
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.putSet); app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.putSet);
app.get("/getIcons",nodes.getIcons); app.get("/getIcons",nodes.getIcons);
app.delete("/nodes/:id",nodes.delete); app.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.delete);
sinon.stub(apiUtil,"determineLangFromHeaders", function() { sinon.stub(apiUtil,"determineLangFromHeaders", function() {
return "en-US"; return "en-US";
}); });
@ -62,10 +48,10 @@ describe("api/admin/nodes", function() {
describe('get nodes', function() { describe('get nodes', function() {
it('returns node list', function(done) { it('returns node list', function(done) {
initNodes({ nodes.init({
nodes:{ nodes:{
getNodeList: function() { getNodeList: function() {
return [1,2,3]; return Promise.resolve([1,2,3]);
} }
} }
}); });
@ -84,10 +70,10 @@ describe("api/admin/nodes", function() {
}); });
it('returns node configs', function(done) { it('returns node configs', function(done) {
initNodes({ nodes.init({
nodes:{ nodes:{
getNodeConfigs: function() { getNodeConfigs: function() {
return "<script></script>"; return Promise.resolve("<script></script>");
} }
}, },
i18n: { i18n: {
@ -108,10 +94,10 @@ describe("api/admin/nodes", function() {
}); });
it('returns node module info', function(done) { it('returns node module info', function(done) {
initNodes({ nodes.init({
nodes:{ nodes:{
getModuleInfo: function(id) { getModuleInfo: function(opts) {
return {"node-red":{name:"node-red"}}[id]; return Promise.resolve({"node-red":{name:"node-red"}}[opts.module]);
} }
} }
}); });
@ -128,10 +114,15 @@ describe("api/admin/nodes", function() {
}); });
it('returns 404 for unknown module', function(done) { it('returns 404 for unknown module', function(done) {
initNodes({ nodes.init({
nodes:{ nodes:{
getModuleInfo: function(id) { getModuleInfo: function(opts) {
return {"node-red":{name:"node-red"}}[id]; var errInstance = new Error("Not Found");
errInstance.code = "not_found";
errInstance.status = 404;
var p = Promise.reject(errInstance);
p.catch(()=>{});
return p;
} }
} }
}); });
@ -147,10 +138,10 @@ describe("api/admin/nodes", function() {
}); });
it('returns individual node info', function(done) { it('returns individual node info', function(done) {
initNodes({ nodes.init({
nodes:{ nodes:{
getNodeInfo: function(id) { getNodeInfo: function(opts) {
return {"node-red/123":{id:"node-red/123"}}[id]; return Promise.resolve({"node-red/123":{id:"node-red/123"}}[opts.id]);
} }
} }
}); });
@ -168,10 +159,10 @@ describe("api/admin/nodes", function() {
}); });
it('returns individual node configs', function(done) { it('returns individual node configs', function(done) {
initNodes({ nodes.init({
nodes:{ nodes:{
getNodeConfig: function(id) { getNodeConfig: function(opts) {
return {"node-red/123":"<script></script>"}[id]; return Promise.resolve({"node-red/123":"<script></script>"}[opts.id]);
} }
}, },
i18n: { i18n: {
@ -190,12 +181,16 @@ describe("api/admin/nodes", function() {
done(); done();
}); });
}); });
it('returns 404 for unknown node', function(done) { it('returns 404 for unknown node', function(done) {
initNodes({ nodes.init({
nodes:{ nodes:{
getNodeInfo: function(id) { getNodeInfo: function(opts) {
return {"node-red/123":{id:"node-red/123"}}[id]; var errInstance = new Error("Not Found");
errInstance.code = "not_found";
errInstance.status = 404;
var p = Promise.reject(errInstance);
p.catch(()=>{});
return p;
} }
} }
}); });
@ -213,46 +208,13 @@ describe("api/admin/nodes", function() {
}); });
describe('install', function() { describe('install', function() {
it('returns 400 if settings are unavailable', function(done) {
initNodes({
settings:{available:function(){return false}}
});
request(app)
.post('/nodes')
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns 400 if request is invalid', function(done) {
initNodes({
settings:{available:function(){return true}}
});
request(app)
.post('/nodes')
.send({})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
describe('by module', function() {
it('installs the module and returns module info', function(done) { it('installs the module and returns module info', function(done) {
initNodes({ var opts;
settings:{available:function(){return true}}, nodes.init({
nodes:{ nodes:{
getModuleInfo: function(id) { return null; }, addModule: function(_opts) {
installModule: function() { opts = _opts;
return when.resolve({ return Promise.resolve({
name:"foo", name:"foo",
nodes:[{id:"123"}] nodes:[{id:"123"}]
}); });
@ -261,7 +223,7 @@ describe("api/admin/nodes", function() {
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo',version:"1.2.3"})
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
@ -270,85 +232,72 @@ describe("api/admin/nodes", function() {
res.body.should.have.property("name","foo"); res.body.should.have.property("name","foo");
res.body.should.have.property("nodes"); res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("id","123"); res.body.nodes[0].should.have.property("id","123");
opts.should.have.property("module","foo");
opts.should.have.property("version","1.2.3");
done(); done();
}); });
}); });
it('returns error', function(done) {
it('fails the install if already installed', function(done) { nodes.init({
initNodes({
settings:{available:function(){return true}},
nodes:{ nodes:{
getModuleInfo: function(id) { return {nodes:{id:"123"}}; }, addModule: function(opts) {
installModule: function() { var errInstance = new Error("Message");
return when.resolve({id:"123"}); errInstance.code = "random_error";
errInstance.status = 400;
var p = Promise.reject(errInstance);
p.catch(()=>{});
return p;
} }
} }
}); });
request(app) request(app)
.post('/nodes') .post('/nodes')
.send({module: 'foo'}) .send({module: 'foo',version:"1.2.3"})
.expect(400) .expect(400)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
throw err; throw err;
} }
res.body.should.have.a.property('code','random_error');
done(); done();
}); });
}); });
it('fails the install if module error', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return null },
installModule: function() {
return when.reject(new Error("test error"));
}
}
});
request(app)
.post('/nodes')
.send({module: 'foo'})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("message","Error: test error");
done();
});
});
it('fails the install if module not found', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return null },
installModule: function() {
var err = new Error("test error");
err.code = 404;
return when.reject(err);
}
}
});
request(app)
.post('/nodes')
.send({module: 'foo'})
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
});
}); });
describe('delete', function() { describe('delete', function() {
it('returns 400 if settings are unavailable', function(done) { it('uninstalls the module', function(done) {
initNodes({ var opts;
settings:{available:function(){return false}} nodes.init({
nodes:{
removeModule: function(_opts) {
opts = _opts;
return Promise.resolve();
}
}
});
request(app)
.del('/nodes/123')
.expect(204)
.end(function(err,res) {
if (err) {
throw err;
}
opts.should.have.property("module","123");
done();
});
});
it('returns error', function(done) {
nodes.init({
nodes:{
removeModule: function(opts) {
var errInstance = new Error("Message");
errInstance.code = "random_error";
errInstance.status = 400;
var p = Promise.reject(errInstance);
p.catch(()=>{});
return p;
}
}
}); });
request(app) request(app)
.del('/nodes/123') .del('/nodes/123')
.expect(400) .expect(400)
@ -356,94 +305,19 @@ describe("api/admin/nodes", function() {
if (err) { if (err) {
throw err; throw err;
} }
res.body.should.have.a.property('code','random_error');
done(); done();
}); });
}); });
});
describe('by module', function() { describe('enable/disable node set', function() {
it('uninstalls the module', function(done) { it('returns 400 for invalid request payload', function(done) {
initNodes({ nodes.init({
settings:{available:function(){return true}},
nodes:{ nodes:{
getModuleInfo: function(id) { return {nodes:[{id:"123"}]} }, setNodeSetState: function(opts) {return Promise.resolve()}
getNodeInfo: function() { return null },
uninstallModule: function() { return when.resolve({id:"123"});}
} }
}); });
request(app)
.del('/nodes/foo')
.expect(204)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('fails the uninstall if the module is not installed', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return null },
getNodeInfo: function() { return null }
}
});
request(app)
.del('/nodes/foo')
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('fails the uninstall if the module is not installed', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return {nodes:[{id:"123"}]} },
getNodeInfo: function() { return null },
uninstallModule: function() { return when.reject(new Error("test error"));}
}
});
request(app)
.del('/nodes/foo')
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("message","Error: test error");
done();
});
});
});
});
describe('enable/disable', function() {
it('returns 400 if settings are unavailable', function(done) {
initNodes({
settings:{available:function(){return false}}
});
request(app)
.put('/nodes/123')
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns 400 for invalid node payload', function(done) {
initNodes({
settings:{available:function(){return true}}
});
request(app) request(app)
.put('/nodes/node-red/foo') .put('/nodes/node-red/foo')
.send({}) .send({})
@ -452,77 +326,23 @@ describe("api/admin/nodes", function() {
if (err) { if (err) {
throw err; throw err;
} }
res.body.should.have.property("code","invalid_request");
res.body.should.have.property("message","Invalid request"); res.body.should.have.property("message","Invalid request");
done(); done();
}); });
}); });
it('returns 400 for invalid module payload', function(done) { it('sets node state and returns node info', function(done) {
initNodes({ var opts;
settings:{available:function(){return true}} nodes.init({
});
request(app)
.put('/nodes/foo')
.send({})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("message","Invalid request");
done();
});
});
it('returns 404 for unknown node', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{ nodes:{
getNodeInfo: function() { return null } setNodeSetState: function(_opts) {
opts = _opts;
return Promise.resolve({id:"123",enabled: true });
}
} }
}); });
request(app)
.put('/nodes/node-red/foo')
.send({enabled:false})
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns 404 for unknown module', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return null }
}
});
request(app)
.put('/nodes/node-blue')
.send({enabled:false})
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('enables disabled node', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getNodeInfo: function() { return {id:"123",enabled: false} },
enableNode: function() { 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})
@ -533,130 +353,41 @@ describe("api/admin/nodes", function() {
} }
res.body.should.have.property("id","123"); res.body.should.have.property("id","123");
res.body.should.have.property("enabled",true); res.body.should.have.property("enabled",true);
opts.should.have.property("enabled",true);
opts.should.have.property("id","node-red/foo");
done(); done();
}); });
}); });
});
it('disables enabled node', function(done) { describe('enable/disable module' ,function() {
initNodes({ it('returns 400 for invalid request payload', function(done) {
settings:{available:function(){return true}}, nodes.init({
nodes:{ nodes:{
getNodeInfo: function() { return {id:"123",enabled: true} }, setModuleState: function(opts) {return Promise.resolve()}
disableNode: function() { return when.resolve({id:"123",enabled: false,types:['a']}); }
} }
}); });
request(app) request(app)
.put('/nodes/node-red/foo') .put('/nodes/node-red')
.send({enabled:false}) .send({})
.expect(200) .expect(400)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
throw err; throw err;
} }
res.body.should.have.property("id","123"); res.body.should.have.property("code","invalid_request");
res.body.should.have.property("enabled",false); res.body.should.have.property("message","Invalid request");
done(); done();
}); });
}); });
it('sets module state and returns module info', function(done) {
describe('no-ops if already in the right state', function() { var opts;
function run(state,done) { nodes.init({
var enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) });
var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) });
initNodes({
settings:{available:function(){return true}},
nodes:{ nodes:{
getNodeInfo: function() { return {id:"123",enabled: state} }, setModuleState: function(_opts) {
enableNode: enableNode, opts = _opts;
disableNode: disableNode return Promise.resolve({name:"node-red"});
} }
});
request(app)
.put('/nodes/node-red/foo')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
if (err) {
throw err;
}
enableNodeCalled.should.be.false();
disableNodeCalled.should.be.false();
res.body.should.have.property("id","123");
res.body.should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
describe('does not no-op if err on node', function() {
function run(state,done) {
var enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) });
var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) });
initNodes({
settings:{available:function(){return true}},
nodes:{
getNodeInfo: function() { return {id:"123",enabled: state, err:"foo"} },
enableNode: enableNode,
disableNode: disableNode
}
});
request(app)
.put('/nodes/node-red/foo')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
if (err) {
throw err;
}
enableNodeCalled.should.be.equal(state);
disableNodeCalled.should.be.equal(!state);
res.body.should.have.property("id","123");
res.body.should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
it('enables disabled module', function(done) {
var n1 = {id:"123",enabled:false,types:['a']};
var n2 = {id:"456",enabled:false,types:['b']};
var enableNode = sinon.stub();
enableNode.onFirstCall().returns((function() {
n1.enabled = true;
return when.resolve(n1);
})());
enableNode.onSecondCall().returns((function() {
n2.enabled = true;
return when.resolve(n2);
})());
enableNode.returns(null);
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function() { return {name:"node-red", nodes:[n1, n2]} },
enableNode: enableNode
} }
}); });
@ -669,154 +400,20 @@ describe("api/admin/nodes", function() {
throw err; throw err;
} }
res.body.should.have.property("name","node-red"); res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes"); opts.should.have.property("enabled",true);
res.body.nodes[0].should.have.property("enabled",true); opts.should.have.property("module","node-red");
res.body.nodes[1].should.have.property("enabled",true);
done(); done();
}); });
}); });
it('disables enabled module', function(done) {
var n1 = {id:"123",enabled:true,types:['a']};
var n2 = {id:"456",enabled:true,types:['b']};
var disableNode = sinon.stub();
disableNode.onFirstCall().returns((function() {
n1.enabled = false;
return when.resolve(n1);
})());
disableNode.onSecondCall().returns((function() {
n2.enabled = false;
return when.resolve(n2);
})());
disableNode.returns(null);
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function() { return {name:"node-red", nodes:[n1, n2]} },
disableNode: disableNode
}
});
request(app)
.put('/nodes/node-red')
.send({enabled:false})
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",false);
res.body.nodes[1].should.have.property("enabled",false);
done();
});
});
describe('no-ops if a node in module already in the right state', function() {
function run(state,done) {
var node = {id:"123",enabled:state,types:['a']};
var enableNode = sinon.spy(function(id) {
node.enabled = true;
return when.resolve(node);
});
var disableNode = sinon.spy(function(id) {
node.enabled = false;
return when.resolve(node);
});
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function() { return {name:"node-red", nodes:[node]}; },
enableNode: enableNode,
disableNode: disableNode
}
});
request(app)
.put('/nodes/node-red')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
if (err) {
throw err;
}
enableNodeCalled.should.be.false();
disableNodeCalled.should.be.false();
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
describe('does not no-op if err on a node in module', function() {
function run(state,done) {
var node = {id:"123",enabled:state,types:['a'],err:"foo"};
var enableNode = sinon.spy(function(id) {
node.enabled = true;
return when.resolve(node);
});
var disableNode = sinon.spy(function(id) {
node.enabled = false;
return when.resolve(node);
});
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function() { return {name:"node-red", nodes:[node]}; },
enableNode: enableNode,
disableNode: disableNode
}
});
request(app)
.put('/nodes/node-red')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
if (err) {
throw err;
}
enableNodeCalled.should.be.equal(state);
disableNodeCalled.should.be.equal(!state);
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
}); });
describe('get icons', function() { describe('get icons', function() {
it('returns icon list', function(done) { it('returns icon list', function(done) {
initNodes({ nodes.init({
nodes:{ nodes:{
getNodeIcons: function() { getIconList: function() {
return {"module":["1.png","2.png","3.png"]}; return Promise.resolve({module:[1,2,3]});
} }
} }
}); });
@ -827,7 +424,6 @@ describe("api/admin/nodes", function() {
if (err) { if (err) {
throw err; throw err;
} }
console.log(res.body);
res.body.should.have.property("module"); res.body.should.have.property("module");
res.body.module.should.be.an.Array(); res.body.module.should.be.an.Array();
res.body.module.should.have.lengthOf(3); res.body.module.should.have.lengthOf(3);

View File

@ -23,6 +23,7 @@ var passport = require("passport");
var auth = require("../../../../red/api/auth"); 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 Permissions = require("../../../../red/api/auth/permissions");
describe("api/auth/index",function() { describe("api/auth/index",function() {
@ -30,7 +31,7 @@ describe("api/auth/index",function() {
describe("ensureClientSecret", function() { describe("ensureClientSecret", function() {
before(function() { before(function() {
auth.init({settings:{},log:{audit:function(){}}}) auth.init({},{})
}); });
it("leaves client_secret alone if not present",function(done) { it("leaves client_secret alone if not present",function(done) {
var req = { var req = {
@ -85,7 +86,7 @@ describe("api/auth/index",function() {
Users.init.restore(); Users.init.restore();
}); });
it("returns login details - credentials", function(done) { it("returns login details - credentials", function(done) {
auth.init({settings:{adminAuth:{type:"credentials"}},log:{audit:function(){}}}) auth.init({adminAuth:{type:"credentials"}},{})
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");
@ -94,14 +95,14 @@ describe("api/auth/index",function() {
}}); }});
}); });
it("returns login details - none", function(done) { it("returns login details - none", function(done) {
auth.init({settings:{},log:{audit:function(){}}}) auth.init({},{})
auth.login(null,{json: function(resp) { auth.login(null,{json: function(resp) {
resp.should.eql({}); resp.should.eql({});
done(); done();
}}); }});
}); });
it("returns login details - strategy", function(done) { it("returns login details - strategy", function(done) {
auth.init({settings:{adminAuth:{type:"strategy",strategy:{label:"test-strategy",icon:"test-icon"}}},log:{audit:function(){}}}) auth.init({adminAuth:{type:"strategy",strategy:{label:"test-strategy",icon:"test-icon"}}},{})
auth.login(null,{json: function(resp) { auth.login(null,{json: function(resp) {
resp.should.have.a.property("type","strategy"); resp.should.have.a.property("type","strategy");
resp.should.have.a.property("prompts"); resp.should.have.a.property("prompts");
@ -115,5 +116,100 @@ describe("api/auth/index",function() {
}); });
}); });
describe("needsPermission", function() {
beforeEach(function() {
sinon.stub(Tokens,"init",function(){});
sinon.stub(Users,"init",function(){});
});
afterEach(function() {
Tokens.init.restore();
Users.init.restore();
if (passport.authenticate.restore) {
passport.authenticate.restore();
}
if (Permissions.hasPermission.restore) {
Permissions.hasPermission.restore();
}
});
it('no-ops if adminAuth not set', function(done) {
sinon.stub(passport,"authenticate",function(scopes,opts) {
return function(req,res,next) {
}
});
auth.init({});
var func = auth.needsPermission("foo");
func({},{},function() {
passport.authenticate.called.should.be.false();
done();
})
});
it('skips auth if req.user undefined', function(done) {
sinon.stub(passport,"authenticate",function(scopes,opts) {
return function(req,res,next) {
next();
}
});
sinon.stub(Permissions,"hasPermission",function(perm) { return true });
auth.init({adminAuth:{}});
var func = auth.needsPermission("foo");
func({user:null},{},function() {
try {
passport.authenticate.called.should.be.true();
Permissions.hasPermission.called.should.be.false();
done();
} catch(err) {
done(err);
}
})
});
it('passes for valid user permission', function(done) {
sinon.stub(passport,"authenticate",function(scopes,opts) {
return function(req,res,next) {
next();
}
});
sinon.stub(Permissions,"hasPermission",function(perm) { return true });
auth.init({adminAuth:{}});
var func = auth.needsPermission("foo");
func({user:true,authInfo: { scope: "read"}},{},function() {
try {
passport.authenticate.called.should.be.true();
Permissions.hasPermission.called.should.be.true();
Permissions.hasPermission.lastCall.args[0].should.eql("read");
Permissions.hasPermission.lastCall.args[1].should.eql("foo");
done();
} catch(err) {
done(err);
}
})
});
it('rejects for invalid user permission', function(done) {
sinon.stub(passport,"authenticate",function(scopes,opts) {
return function(req,res,next) {
next();
}
});
sinon.stub(Permissions,"hasPermission",function(perm) { return false });
auth.init({adminAuth:{}});
var func = auth.needsPermission("foo");
func({user:true,authInfo: { scope: "read"}},{
status: function(status) {
return { end: function() {
try {
status.should.eql(401);
done();
} catch(err) {
done(err);
}
}}
}
},function() {
done(new Error("hasPermission unexpected passed"))
});
});
});
}); });

View File

@ -24,9 +24,6 @@ var Tokens = require("../../../../red/api/auth/tokens");
var Clients = require("../../../../red/api/auth/clients"); var Clients = require("../../../../red/api/auth/clients");
describe("api/auth/strategies", function() { describe("api/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() {

View File

@ -33,6 +33,24 @@ var listenPort = 0; // use ephemeral port
describe("api/editor/comms", function() { describe("api/editor/comms", function() {
var connections = [];
var mockComms = {
addConnection: function(opts) {
connections.push(opts.client);
return Promise.resolve()
},
removeConnection: function(opts) {
for (var i=0;i<connections.length;i++) {
if (connections[i] === opts.client) {
connections.splice(i,1);
break;
}
}
return Promise.resolve()
},
subscribe: function() { return Promise.resolve()},
unsubscribe: function() { return Promise.resolve(); }
}
describe("with default keepalive", function() { describe("with default keepalive", function() {
var server; var server;
@ -41,11 +59,7 @@ describe("api/editor/comms", function() {
before(function(done) { before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);}); sinon.stub(Users,"default",function() { return when.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)})); server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, { comms.init(server, {}, {comms: mockComms});
settings:{},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address); server.listen(listenPort, address);
server.on('listening', function() { server.on('listening', function() {
port = server.address().port; port = server.address().port;
@ -63,9 +77,15 @@ describe("api/editor/comms", function() {
it('accepts connection', function(done) { it('accepts connection', function(done) {
var ws = new WebSocket(url); var ws = new WebSocket(url);
connections.length.should.eql(0);
ws.on('open', function() { ws.on('open', function() {
try {
connections.length.should.eql(1);
ws.close(); ws.close();
done(); done();
} catch(err) {
done(err);
}
}); });
}); });
@ -73,7 +93,8 @@ describe("api/editor/comms", function() {
var ws = new WebSocket(url); var ws = new WebSocket(url);
ws.on('open', function() { ws.on('open', function() {
ws.send('{"subscribe":"topic1"}'); ws.send('{"subscribe":"topic1"}');
comms.publish('topic1', 'foo'); connections.length.should.eql(1);
connections[0].send('topic1', 'foo');
}); });
ws.on('message', function(msg) { ws.on('message', function(msg) {
msg.should.equal('[{"topic":"topic1","data":"foo"}]'); msg.should.equal('[{"topic":"topic1","data":"foo"}]');
@ -82,43 +103,13 @@ describe("api/editor/comms", function() {
}); });
}); });
it('publishes retained message for subscription', function(done) {
comms.publish('topic2', 'bar', true);
var ws = new WebSocket(url);
ws.on('open', function() {
ws.send('{"subscribe":"topic2"}');
});
ws.on('message', function(msg) {
console.log(msg);
msg.should.equal('[{"topic":"topic2","data":"bar"}]');
ws.close();
done();
});
});
it('retained message is deleted by non-retained message', function(done) {
comms.publish('topic3', 'retained', true);
comms.publish('topic3', 'non-retained');
var ws = new WebSocket(url);
ws.on('open', function() {
ws.send('{"subscribe":"topic3"}');
comms.publish('topic3', 'new');
});
ws.on('message', function(msg) {
console.log(msg);
msg.should.equal('[{"topic":"topic3","data":"new"}]');
ws.close();
done();
});
});
it('malformed messages are ignored',function(done) { it('malformed messages are ignored',function(done) {
var ws = new WebSocket(url); var ws = new WebSocket(url);
ws.on('open', function() { ws.on('open', function() {
ws.send('not json'); ws.send('not json');
ws.send('[]'); ws.send('[]');
ws.send('{"subscribe":"topic3"}'); ws.send('{"subscribe":"topic3"}');
comms.publish('topic3', 'correct'); connections[0].send('topic3', 'correct');
}); });
ws.on('message', function(msg) { ws.on('message', function(msg) {
console.log(msg); console.log(msg);
@ -127,40 +118,16 @@ describe("api/editor/comms", function() {
done(); done();
}); });
}); });
// The following test currently fails due to minimum viable
// implementation. More test should be written to test topic
// matching once this one is passing
it.skip('receives message on correct topic', function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
ws.send('{"subscribe":"topic4"}');
comms.publish('topic5', 'foo');
comms.publish('topic4', 'bar');
});
ws.on('message', function(msg) {
console.log(msg);
msg.should.equal('[{"topic":"topic4","data":"bar"}]');
ws.close();
done();
});
}); });
it('listens for node/status events');
});
describe("disabled editor", function() { describe("disabled editor", function() {
var server; var server;
var url; var url;
var port; var port;
before(function(done) { before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);}); sinon.stub(Users,"default",function() { return Promise.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)})); server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, { comms.init(server, {disableEditor:true}, {comms: mockComms});
settings:{disableEditor:true},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address); server.listen(listenPort, address);
server.on('listening', function() { server.on('listening', function() {
port = server.address().port; port = server.address().port;
@ -177,12 +144,14 @@ describe("api/editor/comms", function() {
}); });
it('rejects websocket connections',function(done) { it('rejects websocket connections',function(done) {
connections.length.should.eql(0);
var ws = new WebSocket(url); var ws = new WebSocket(url);
ws.on('open', function() { ws.on('open', function() {
done(new Error("Socket connection unexpectedly accepted")); done(new Error("Socket connection unexpectedly accepted"));
ws.close(); ws.close();
}); });
ws.on('error', function() { ws.on('error', function() {
connections.length.should.eql(0);
done(); done();
}); });
@ -196,11 +165,7 @@ describe("api/editor/comms", function() {
before(function(done) { before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);}); sinon.stub(Users,"default",function() { return when.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)})); server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, { comms.init(server, {httpAdminRoot:"/adminPath"}, {comms: mockComms});
settings:{httpAdminRoot:"/adminPath"},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address); server.listen(listenPort, address);
server.on('listening', function() { server.on('listening', function() {
port = server.address().port; port = server.address().port;
@ -217,8 +182,10 @@ describe("api/editor/comms", function() {
}); });
it('accepts connections',function(done) { it('accepts connections',function(done) {
connections.length.should.eql(0);
var ws = new WebSocket(url); var ws = new WebSocket(url);
ws.on('open', function() { ws.on('open', function() {
connections.length.should.eql(1);
ws.close(); ws.close();
done(); done();
}); });
@ -236,11 +203,7 @@ describe("api/editor/comms", function() {
before(function(done) { before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);}); sinon.stub(Users,"default",function() { return when.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)})); server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server,{ comms.init(server, {httpAdminRoot:"/adminPath/"}, {comms: mockComms});
settings:{httpAdminRoot:"/adminPath"},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address); server.listen(listenPort, address);
server.on('listening', function() { server.on('listening', function() {
port = server.address().port; port = server.address().port;
@ -257,8 +220,10 @@ describe("api/editor/comms", function() {
}); });
it('accepts connections',function(done) { it('accepts connections',function(done) {
connections.length.should.eql(0);
var ws = new WebSocket(url); var ws = new WebSocket(url);
ws.on('open', function() { ws.on('open', function() {
connections.length.should.eql(1);
ws.close(); ws.close();
done(); done();
}); });
@ -276,11 +241,7 @@ describe("api/editor/comms", function() {
before(function(done) { before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);}); sinon.stub(Users,"default",function() { return when.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)})); server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, { comms.init(server, {httpAdminRoot:"adminPath"}, {comms: mockComms});
settings:{httpAdminRoot:"adminPath"},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address); server.listen(listenPort, address);
server.on('listening', function() { server.on('listening', function() {
port = server.address().port; port = server.address().port;
@ -297,8 +258,10 @@ describe("api/editor/comms", function() {
}); });
it('accepts connections',function(done) { it('accepts connections',function(done) {
connections.length.should.eql(0);
var ws = new WebSocket(url); var ws = new WebSocket(url);
ws.on('open', function() { ws.on('open', function() {
connections.length.should.eql(1);
ws.close(); ws.close();
done(); done();
}); });
@ -316,11 +279,7 @@ describe("api/editor/comms", function() {
before(function(done) { before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);}); sinon.stub(Users,"default",function() { return when.resolve(null);});
server = stoppable(http.createServer(function(req,res){app(req,res)})); server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, { comms.init(server, {webSocketKeepAliveTime: 100}, {comms: mockComms});
settings:{webSocketKeepAliveTime: 100},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address); server.listen(listenPort, address);
server.on('listening', function() { server.on('listening', function() {
port = server.address().port; port = server.address().port;
@ -355,7 +314,7 @@ describe("api/editor/comms", function() {
ws.on('open', function() { ws.on('open', function() {
ws.send('{"subscribe":"foo"}'); ws.send('{"subscribe":"foo"}');
interval = setInterval(function() { interval = setInterval(function() {
comms.publish('foo', 'bar'); connections[0].send('foo', 'bar');
}, 50); }, 50);
}); });
ws.on('message', function(data) { ws.on('message', function(data) {
@ -403,11 +362,7 @@ describe("api/editor/comms", function() {
server = stoppable(http.createServer(function(req,res){app(req,res)})); server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server,{ comms.init(server, {adminAuth:{}}, {comms: mockComms});
settings:{adminAuth:{}},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address); server.listen(listenPort, address);
server.on('listening', function() { server.on('listening', function() {
port = server.address().port; port = server.address().port;
@ -447,7 +402,7 @@ describe("api/editor/comms", function() {
if (received == 1) { if (received == 1) {
msg.should.equal('{"auth":"ok"}'); msg.should.equal('{"auth":"ok"}');
ws.send('{"subscribe":"foo"}'); ws.send('{"subscribe":"foo"}');
comms.publish('foo', 'correct'); connections[0].send('foo', 'correct');
} else { } else {
msg.should.equal('[{"topic":"foo","data":"correct"}]'); msg.should.equal('[{"topic":"foo","data":"correct"}]');
ws.close(); ws.close();
@ -494,11 +449,7 @@ describe("api/editor/comms", function() {
before(function(done) { before(function(done) {
getDefaultUser = sinon.stub(Users,"default",function() { return when.resolve({permissions:"read"});}); getDefaultUser = sinon.stub(Users,"default",function() { return when.resolve({permissions:"read"});});
server = stoppable(http.createServer(function(req,res){app(req,res)})); server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, { comms.init(server, {adminAuth:{}}, {comms: mockComms});
settings:{adminAuth:{}},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address); server.listen(listenPort, address);
server.on('listening', function() { server.on('listening', function() {
port = server.address().port; port = server.address().port;
@ -520,7 +471,7 @@ describe("api/editor/comms", function() {
ws.on('open', function() { ws.on('open', function() {
ws.send('{"subscribe":"foo"}'); ws.send('{"subscribe":"foo"}');
setTimeout(function() { setTimeout(function() {
comms.publish('foo', 'correct'); connections[0].send('foo', 'correct');
},200); },200);
}); });
ws.on('message', function(msg) { ws.on('message', function(msg) {

View File

@ -29,64 +29,30 @@ describe('api/editor/credentials', function() {
app = express(); app = express();
app.get('/credentials/:type/:id',credentials.get); app.get('/credentials/:type/:id',credentials.get);
credentials.init({ credentials.init({
log:{audit:function(){}}, flows: {
nodes:{ getNodeCredentials: function(opts) {
getCredentials: function(id) { if (opts.type === "known-type" && opts.id === "n1") {
if (id === "n1") { return Promise.resolve({
return {user1:"abc",password1:"123"}; user1:"abc",
has_password1: true
});
} else { } else {
return null; var err = new Error("message");
} err.code = "test_code";
}, var p = Promise.reject(err);
getCredentialDefinition:function(type) { p.catch(()=>{});
if (type === "known-type") { return p;
return {user1:{type:"text"},password1:{type:"password"}};
} else {
return null;
} }
} }
} }
}); });
}); });
it('returns empty credentials if unknown type',function(done) {
request(app)
.get("/credentials/unknown-type/n1")
.expect(200)
.expect("Content-Type",/json/)
.end(function(err,res) {
if (err) {
done(err);
} else {
try {
res.body.should.eql({});
done();
} catch(e) {
done(e);
}
}
})
});
it('returns empty credentials if none are stored',function(done) {
request(app)
.get("/credentials/known-type/n2")
.expect("Content-Type",/json/)
.end(function(err,res) {
if (err) {
done(err);
} else {
try {
res.body.should.eql({});
done();
} catch(e) {
done(e);
}
}
})
});
it('returns stored credentials',function(done) { it('returns stored credentials',function(done) {
request(app) request(app)
.get("/credentials/known-type/n1") .get("/credentials/known-type/n1")
.expect("Content-Type",/json/) .expect("Content-Type",/json/)
.expect(200)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
done(err); done(err);
@ -102,5 +68,26 @@ describe('api/editor/credentials', function() {
} }
}) })
}); });
it('returns any error',function(done) {
request(app)
.get("/credentials/unknown-type/n2")
.expect("Content-Type",/json/)
.expect(500)
.end(function(err,res) {
if (err) {
done(err);
} else {
try {
res.body.should.have.property('code');
res.body.code.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal('message');
done();
} catch(e) {
done(e);
}
}
})
});
}); });

View File

@ -22,6 +22,10 @@ var editorApi = require("../../../../red/api/editor");
var comms = require("../../../../red/api/editor/comms"); var comms = require("../../../../red/api/editor/comms");
var info = require("../../../../red/api/editor/settings"); var info = require("../../../../red/api/editor/settings");
var auth = require("../../../../red/api/auth"); var auth = require("../../../../red/api/auth");
var log = require("../../../../red/util/log");
var when = require("when"); var when = require("when");
@ -37,9 +41,7 @@ describe("api/editor/index", function() {
info.init.restore(); info.init.restore();
}); });
it("disables the editor", function() { it("disables the editor", function() {
var editorApp = editorApi.init({},{ var editorApp = editorApi.init({},{disableEditor:true},{});
settings:{disableEditor:true}
});
should.not.exist(editorApp); should.not.exist(editorApp);
comms.init.called.should.be.false(); comms.init.called.should.be.false();
info.init.called.should.be.false(); info.init.called.should.be.false();
@ -67,15 +69,13 @@ describe("api/editor/index", function() {
}) })
require("../../../../red/api/editor/theme").app.restore(); require("../../../../red/api/editor/theme").app.restore();
auth.needsPermission.restore(); auth.needsPermission.restore();
log.error.restore();
}); });
before(function() { before(function() {
app = editorApi.init({},{ sinon.stub(log,"error",function(err) { errors.push(err)})
log:{audit:function(){},error:function(msg){errors.push(msg)}}, app = editorApi.init({},{httpNodeRoot:true, httpAdminRoot: true,disableEditor:false,exportNodeSettings:function(){}},{
settings:{httpNodeRoot:true, httpAdminRoot: true,disableEditor:false,exportNodeSettings:function(){}}, isStarted: () => Promise.resolve(isStarted)
events:{on:function(){},removeListener:function(){}},
isStarted: function() { return isStarted; },
nodes: {paletteEditorEnabled: function() { return false }}
}); });
}); });
it('serves the editor', function(done) { it('serves the editor', function(done) {
@ -117,7 +117,7 @@ describe("api/editor/index", function() {
done(); done();
}); });
}); });
// it('GET /settings', function(done) { // it.skip('GET /settings', function(done) {
// request(app).get("/settings").expect(200).end(function(err,res) { // request(app).get("/settings").expect(200).end(function(err,res) {
// if (err) { // if (err) {
// return done(err); // return done(err);

View File

@ -16,334 +16,287 @@
var should = require("should"); var should = require("should");
var sinon = require("sinon"); var sinon = require("sinon");
var fs = require("fs");
var fspath = require('path');
var request = require('supertest'); var request = require('supertest');
var express = require('express'); var express = require('express');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var when = require('when');
var app; var app;
var library = require("../../../../red/api/editor/library"); var library = require("../../../../red/api/editor/library");
var auth = require("../../../../red/api/auth");
describe("api/editor/library", function() { describe("api/editor/library", function() {
function initLibrary(_flows,_libraryEntries,_examples,_exampleFlowPathFunction) {
var flows = _flows;
var libraryEntries = _libraryEntries;
library.init(app,{
log:{audit:function(){},_:function(){},warn:function(){}},
storage: {
init: function() {
return when.resolve();
},
getAllFlows: function() {
return when.resolve(flows);
},
getFlow: function(fn) {
if (flows[fn]) {
return when.resolve(flows[fn]);
} else if (fn.indexOf("..")!==-1) {
var err = new Error();
err.code = 'forbidden';
return when.reject(err);
} else {
return when.reject();
}
},
saveFlow: function(fn,data) {
if (fn.indexOf("..")!==-1) {
var err = new Error();
err.code = 'forbidden';
return when.reject(err);
}
flows[fn] = data;
return when.resolve();
},
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]) {
return when.resolve(libraryEntries[type][path]);
} else {
return when.reject();
}
},
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;
return when.resolve();
}
},
events: {
on: function(){},
removeListener: function(){}
},
nodes: {
getNodeExampleFlows: function() {
return _examples;
},
getNodeExampleFlowPath: _exampleFlowPathFunction
}
});
}
describe("flows", function() {
before(function() { before(function() {
app = express(); app = express();
app.use(bodyParser.json()); app.use(bodyParser.json());
app.get("/library/flows",library.getAll); app.get("/library/flows",library.getAll);
app.post(new RegExp("/library/flows\/(.*)"),library.post); app.post(/library\/([^\/]+)\/(.*)/,library.saveEntry);
app.get(new RegExp("/library/flows\/(.*)"),library.get); app.get(/library\/([^\/]+)(?:$|\/(.*))/,library.getEntry);
app.response.sendFile = function (path) {
app.response.json.call(this, {sendFile: path});
};
sinon.stub(fs,"statSync",function() { return true; });
}); });
after(function() { after(function() {
fs.statSync.restore();
}); });
it('returns empty result', function(done) { it('returns all flows', function(done) {
initLibrary({},{flows:{}}); library.init({
library: {
getEntries: function(opts) {
return Promise.resolve({a:1,b:2});
}
}
});
request(app) request(app)
.get('/library/flows') .get('/library/flows')
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
throw err; return done(err);
} }
res.body.should.not.have.property('f'); res.body.should.have.property('a',1);
res.body.should.not.have.property('d'); res.body.should.have.property('b',2);
done(); done();
}); });
}); })
it('returns an error on all flows', function(done) {
it('returns 404 for non-existent entry', function(done) { library.init({
initLibrary({},{flows:{}}); library: {
request(app) getEntries: function(opts) {
.get('/library/flows/foo') var err = new Error("message");
.expect(404) err.code = "random_error";
.end(done); err.status = 400;
}); var p = Promise.reject(err);
p.catch(()=>{});
return p;
it('can store and retrieve item', function(done) {
initLibrary({},{flows:{}});
var flow = '[]';
request(app)
.post('/library/flows/foo')
.set('Content-Type', 'application/json')
.send(flow)
.expect(204).end(function (err, res) {
if (err) {
throw err;
} }
request(app)
.get('/library/flows/foo')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
} }
res.text.should.equal(flow);
done();
}); });
});
});
it('lists a stored item', function(done) {
initLibrary({f:["bar"]});
request(app) request(app)
.get('/library/flows') .get('/library/flows')
.expect(200) .expect(400)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
throw err; return done(err);
} }
res.body.should.have.property('f'); res.body.should.have.property('code');
should.deepEqual(res.body.f,['bar']); res.body.code.should.be.equal("random_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("message");
done(); done();
}); });
}); });
it('returns 403 for malicious get attempt', function(done) { it('returns an individual entry - flow type', function(done) {
initLibrary({}); var opts;
// without the userDir override the malicious url would be library.init({
// http://127.0.0.1:1880/library/flows/../../package to library: {
// obtain package.json from the node-red root. getEntry: function(_opts) {
request(app) opts = _opts;
.get('/library/flows/../../../../../package') return Promise.resolve('{"a":1,"b":2}');
.expect(403) }
.end(done); }
}); });
it('returns 403 for malicious post attempt', function(done) {
initLibrary({});
// without the userDir override the malicious url would be
// http://127.0.0.1:1880/library/flows/../../package to
// obtain package.json from the node-red root.
request(app) request(app)
.post('/library/flows/../../../../../package') .get('/library/flows/abc')
.expect(403)
.end(done);
});
it('includes examples flows if set', function(done) {
var examples = {"d":{"node-module":{"f":["example-one"]}}};
initLibrary({},{},examples);
request(app)
.get('/library/flows')
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
throw err; return done(err);
} }
res.body.should.have.property('d'); res.body.should.have.property('a',1);
res.body.d.should.have.property('_examples_'); res.body.should.have.property('b',2);
should.deepEqual(res.body.d._examples_,examples); opts.should.have.property('type','flows');
opts.should.have.property('path','abc');
done(); done();
}); });
}); })
it('returns a directory listing - flow type', function(done) {
it('can retrieve an example flow', function(done) { var opts;
var examples = {"d":{"node-module":{"f":["example-one"]}}}; library.init({
initLibrary({},{},examples,function(module,path) { library: {
return module + ':' + path getEntry: function(_opts) {
opts = _opts;
return Promise.resolve({"a":1,"b":2});
}
}
}); });
request(app) request(app)
.get('/library/flows/_examples_/node-module/example-one') .get('/library/flows/abc/def')
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
throw err; return done(err);
} }
res.body.should.have.property('sendFile', res.body.should.have.property('a',1);
'node-module:example-one'); res.body.should.have.property('b',2);
opts.should.have.property('type','flows');
opts.should.have.property('path','abc/def');
done(); done();
}); });
}); })
it('returns an individual entry - non-flow type', function(done) {
it('can retrieve an example flow in an org scoped package', function(done) { var opts;
var examples = {"d":{"@org_scope/node_package":{"f":["example-one"]}}}; library.init({
initLibrary({},{},examples,function(module,path) { library: {
return module + ':' + path getEntry: function(_opts) {
opts = _opts;
return Promise.resolve('{"a":1,"b":2}');
}
}
}); });
request(app) request(app)
.get('/library/flows/_examples_/@org_scope/node_package/example-one') .get('/library/non-flow/abc')
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
throw err; return done(err);
} }
res.body.should.have.property('sendFile', opts.should.have.property('type','non-flow');
'@org_scope/node_package:example-one'); opts.should.have.property('path','abc');
res.text.should.eql('{"a":1,"b":2}');
done(); done();
}); });
})
it('returns a directory listing - non-flow type', function(done) {
var opts;
library.init({
library: {
getEntry: function(_opts) {
opts = _opts;
return Promise.resolve({"a":1,"b":2});
}
}
}); });
});
describe("type", function() {
before(function() {
app = express();
app.use(bodyParser.json());
initLibrary({},{});
auth.init({settings:{}});
library.register("test");
});
it('returns empty result', function(done) {
initLibrary({},{'test':{"":[]}});
request(app) request(app)
.get('/library/test') .get('/library/non-flow/abc/def')
.expect(200) .expect(200)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
throw err; return done(err);
} }
res.body.should.not.have.property('f'); res.body.should.have.property('a',1);
res.body.should.have.property('b',2);
opts.should.have.property('type','non-flow');
opts.should.have.property('path','abc/def');
done(); done();
}); });
}); })
it('returns 404 for non-existent entry', function(done) { it('returns an error on individual get', function(done) {
initLibrary({},{}); var opts;
request(app) library.init({
.get('/library/test/foo') library: {
.expect(404) getEntry: function(_opts) {
.end(done); opts = _opts;
}); var err = new Error("message");
err.code = "random_error";
it('can store and retrieve item', function(done) { err.status = 400;
initLibrary({},{'test':{}}); var p = Promise.reject(err);
var flow = {text:"test content"}; p.catch(()=>{});
request(app) return p;
.post('/library/test/foo')
.set('Content-Type', 'application/json')
.send(flow)
.expect(204).end(function (err, res) {
if (err) {
throw err;
} }
}
});
request(app) request(app)
.get('/library/test/foo') .get('/library/flows/123')
.expect(200) .expect(400)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
throw err; return done(err);
} }
res.text.should.equal(flow.text); opts.should.have.property('type','flows');
done(); opts.should.have.property('path','123');
});
});
});
it('lists a stored item', function(done) { res.body.should.have.property('code');
initLibrary({},{'test':{'a':['abc','def']}}); res.body.code.should.be.equal("random_error");
request(app) res.body.should.have.property('message');
.get('/library/test/a') res.body.message.should.be.equal("message");
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
// This response isn't strictly accurate - but it
// verifies the api returns what storage gave it
should.deepEqual(res.body,['abc','def']);
done(); done();
}); });
}); });
it('returns 403 for malicious access attempt', function(done) { it('saves an individual entry - flow type', function(done) {
var opts;
library.init({
library: {
saveEntry: function(_opts) {
opts = _opts;
return Promise.resolve();
}
}
});
request(app) request(app)
.get('/library/test/../../../../../../../../../../etc/passwd') .post('/library/flows/abc/def')
.expect(403) .expect(204)
.end(done); .send({a:1,b:2,c:3})
.end(function(err,res) {
if (err) {
return done(err);
}
opts.should.have.property('type','flows');
opts.should.have.property('path','abc/def');
opts.should.have.property('meta',{});
opts.should.have.property('body',JSON.stringify({a:1,b:2,c:3}));
done();
}); });
})
it('returns 403 for malicious access attempt', function(done) { it('saves an individual entry - non-flow type', function(done) {
var opts;
library.init({
library: {
saveEntry: function(_opts) {
opts = _opts;
return Promise.resolve();
}
}
});
request(app) request(app)
.get('/library/test/..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd') .post('/library/non-flow/abc/def')
.expect(403) .expect(204)
.end(done); .send({a:1,b:2,text:"123"})
.end(function(err,res) {
if (err) {
return done(err);
}
opts.should.have.property('type','non-flow');
opts.should.have.property('path','abc/def');
opts.should.have.property('meta',{a:1,b:2});
opts.should.have.property('body',"123");
done();
}); });
})
it('returns 403 for malicious access attempt', function(done) { it('returns an error on individual save', function(done) {
var opts;
library.init({
library: {
saveEntry: function(_opts) {
opts = _opts;
var err = new Error("message");
err.code = "random_error";
err.status = 400;
var p = Promise.reject(err);
p.catch(()=>{});
return p;
}
}
});
request(app) request(app)
.post('/library/test/../../../../../../../../../../etc/passwd') .post('/library/non-flow/abc/def')
.set('Content-Type', 'text/plain') .send({a:1,b:2,text:"123"})
.send('root:x:0:0:root:/root:/usr/bin/tclsh') .expect(400)
.expect(403) .end(function(err,res) {
.end(done); if (err) {
}); return done(err);
}
opts.should.have.property('type','non-flow');
opts.should.have.property('path','abc/def');
res.body.should.have.property('code');
res.body.code.should.be.equal("random_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("message");
done();
});
}); });
}); });

View File

@ -20,6 +20,8 @@ var express = require('express');
var sinon = require('sinon'); var sinon = require('sinon');
var locales = require("../../../../red/api/editor/locales"); var locales = require("../../../../red/api/editor/locales");
var i18n = require("../../../../red/util/i18n");
describe("api/editor/locales", function() { describe("api/editor/locales", function() {
beforeEach(function() { beforeEach(function() {
@ -30,24 +32,18 @@ describe("api/editor/locales", function() {
var app; var app;
before(function() { before(function() {
// bit of a mess of internal workings // bit of a mess of internal workings
locales.init({ locales.init({});
i18n: { sinon.stub(i18n.i,'lng',function() { return 'en-US'});
i: { sinon.stub(i18n.i,'setLng',function(lang,callback) { if (callback) {callback();}});
lng: function() { return 'en-US'}, sinon.stub(i18n,'catalog',function(namespace, lang) {return {namespace:namespace, lang:lang};});
setLng: function(lang,callback) {
if (callback) {
callback();
}
}
},
catalog: function(namespace, lang) {
return {namespace:namespace, lang:lang};
}
}
});
app = express(); app = express();
app.get(/locales\/(.+)\/?$/,locales.get); app.get(/locales\/(.+)\/?$/,locales.get);
}); });
after(function() {
i18n.i.lng.restore();
i18n.i.setLng.restore();
i18n.catalog.restore();
})
it('returns with default language', function(done) { it('returns with default language', function(done) {
request(app) request(app)
.get("/locales/message-catalog") .get("/locales/message-catalog")
@ -79,30 +75,31 @@ describe("api/editor/locales", function() {
var app; var app;
before(function() { before(function() {
// bit of a mess of internal workings // bit of a mess of internal workings
locales.init({ sinon.stub(i18n,'catalog',function(namespace, lang) {
i18n: {
catalog: function(namespace, lang) {
return { return {
"node-red": "should not return", "node-red": "should not return",
"test-module-a-id": "test-module-a-catalog", "test-module-a-id": "test-module-a-catalog",
"test-module-b-id": "test-module-b-catalog", "test-module-b-id": "test-module-b-catalog",
"test-module-c-id": "test-module-c-catalog" "test-module-c-id": "test-module-c-catalog"
}[namespace] }[namespace]
} });
}, locales.init({
nodes: { nodes: {
getNodeList: function() { getNodeList: function(opts) {
return [ return Promise.resolve([
{module:"node-red",id:"node-red-id"}, {module:"node-red",id:"node-red-id"},
{module:"test-module-a",id:"test-module-a-id"}, {module:"test-module-a",id:"test-module-a-id"},
{module:"test-module-b",id:"test-module-b-id"} {module:"test-module-b",id:"test-module-b-id"}
]; ]);
} }
} }
}); });
app = express(); app = express();
app.get("/locales/nodes",locales.getAllNodes); app.get("/locales/nodes",locales.getAllNodes);
}); });
after(function() {
i18n.catalog.restore();
})
it('returns with the node catalogs', function(done) { it('returns with the node catalogs', function(done) {
request(app) request(app)
.get("/locales/nodes") .get("/locales/nodes")

View File

@ -17,215 +17,103 @@
var should = require("should"); var should = require("should");
var request = require('supertest'); var request = require('supertest');
var express = require('express'); var express = require('express');
var bodyParser = require("body-parser");
var sinon = require('sinon'); var sinon = require('sinon');
var when = require('when');
var app = express(); var app;
var info = require("../../../../red/api/editor/settings"); var info = require("../../../../red/api/editor/settings");
var theme = require("../../../../red/api/editor/theme"); var theme = require("../../../../red/api/editor/theme");
describe("api/editor/settings", function() { describe("api/editor/settings", function() {
describe("settings handler", function() {
before(function() { before(function() {
sinon.stub(theme,"settings",function() { return { test: 456 };}); sinon.stub(theme,"settings",function() { return { test: 456 };});
app = express(); app = express();
app.use(bodyParser.json());
app.get("/settings",info.runtimeSettings); app.get("/settings",info.runtimeSettings);
app.get("/settings/user",function(req,res,next) {req.user = "fred"; next()}, info.userSettings);
app.post("/settings/user",function(req,res,next) {req.user = "fred"; next()},info.updateUserSettings);
}); });
after(function() { after(function() {
theme.settings.restore(); theme.settings.restore();
}); });
it('returns the filtered settings', function(done) { it('returns the runtime settings', function(done) {
info.init({ info.init({
settings: { settings: {
foo: 123, getRuntimeSettings: function(opts) {
httpNodeRoot: "testHttpNodeRoot", return Promise.resolve({
version: "testVersion", a:1,
paletteCategories :["red","blue","green"], b:2
exportNodeSettings: function(obj) {
obj.testNodeSetting = "helloWorld";
}
},
nodes: {
paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
res.body.should.have.property("version","testVersion");
res.body.should.have.property("paletteCategories",["red","blue","green"]);
res.body.should.have.property("editorTheme",{test:456});
res.body.should.have.property("testNodeSetting","helloWorld");
res.body.should.not.have.property("foo",123);
res.body.should.have.property("flowEncryptionType","test-key-type");
done();
});
});
it('includes project settings if projects available', function(done) {
info.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function(obj) {
obj.testNodeSetting = "helloWorld";
}
},
nodes: {
paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {
projects: {
getActiveProject: () => 'test-active-project',
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("project","test-active-project");
res.body.should.not.have.property("files");
res.body.should.have.property("git");
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
done();
});
});
it('includes existing files details if projects enabled but no active project and files exist', function(done) {
info.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function(obj) {
obj.testNodeSetting = "helloWorld";
}
},
nodes: {
paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {
projects: {
flowFileExists: () => true,
getActiveProject: () => false,
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.not.have.property("project");
res.body.should.have.property("files");
res.body.files.should.have.property("flow",'test-flow-file');
res.body.files.should.have.property("credentials",'test-creds-file');
res.body.should.have.property("git");
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
done();
});
});
it('does not include file details if projects enabled but no active project and files do not exist', function(done) {
info.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function(obj) {
obj.testNodeSetting = "helloWorld";
}
},
nodes: {
paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {
projects: {
flowFileExists: () => false,
getActiveProject: () => false,
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.not.have.property("project");
res.body.should.not.have.property("files");
res.body.should.have.property("git");
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
done();
});
});
it('overrides palette editable if runtime says it is disabled', function(done) {
info.init({
settings: {
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function() {}
},
nodes: {
paletteEditorEnabled: function() { return false; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
res.body.should.have.property("version","testVersion");
res.body.should.have.property("paletteCategories",["red","blue","green"]);
res.body.should.have.property("editorTheme");
res.body.editorTheme.should.have.property("test",456);
res.body.editorTheme.should.have.property("palette",{editable:false});
done();
});
}) })
}
}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("a",1);
res.body.should.have.property("b",2);
res.body.should.have.property("editorTheme",{test:456});
done();
});
});
it('returns the user settings', function(done) {
info.init({
settings: {
getUserSettings: function(opts) {
if (opts.user !== "fred") {
return Promise.reject(new Error("Unknown user"));
}
return Promise.resolve({
c:3,
d:4
})
}
}
});
request(app)
.get("/settings/user")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.eql({c:3,d:4});
done();
});
});
it('updates the user settings', function(done) {
var update;
info.init({
settings: {
updateUserSettings: function(opts) {
if (opts.user !== "fred") {
return Promise.reject(new Error("Unknown user"));
}
update = opts.settings;
return Promise.resolve()
}
}
});
request(app)
.post("/settings/user")
.send({
e:4,
f:5
})
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
update.should.eql({e:4,f:5});
done();
});
}); });
}); });

View File

@ -18,83 +18,42 @@ var should = require("should");
var sinon = require("sinon"); var sinon = require("sinon");
var request = require("supertest"); var request = require("supertest");
var express = require("express"); var express = require("express");
var editorApi = require("../../../../red/api/editor");
var comms = require("../../../../red/api/editor/comms");
var info = require("../../../../red/api/editor/settings");
var auth = require("../../../../red/api/auth");
var sshkeys = require("../../../../red/api/editor/sshkeys"); var sshkeys = require("../../../../red/api/editor/sshkeys");
var when = require("when");
var bodyParser = require("body-parser"); var bodyParser = require("body-parser");
var fs = require("fs-extra");
var fspath = require("path");
describe("api/editor/sshkeys", function() { describe("api/editor/sshkeys", function() {
var app; var app;
var mockList = [
'library','theme','locales','credentials','comms'
]
var isStarted = true;
var errors = [];
var session_data = {};
var mockRuntime = { var mockRuntime = {
settings: { settings: {
httpNodeRoot: true, getUserKeys: function() {},
httpAdminRoot: true, getUserKey: function() {},
disableEditor: false, generateUserKey: function() {},
exportNodeSettings:function(){}, removeUserKey: function() {}
storage: {
getSessions: function(){
return when.resolve(session_data);
},
setSessions: function(_session) {
session_data = _session;
return when.resolve();
} }
} }
},
log:{audit:function(){},error:function(msg){errors.push(msg)},trace:function(){}},
storage: {
projects: {
ssh: {
init: function(){},
listSSHKeys: function(){},
getSSHKey: function(){},
generateSSHKey: function(){},
deleteSSHKey: function(){}
}
}
},
events:{on:function(){},removeListener:function(){}},
isStarted: function() { return isStarted; },
nodes: {paletteEditorEnabled: function() { return false }}
};
before(function() { before(function() {
auth.init(mockRuntime); sshkeys.init(mockRuntime);
app = express(); app = express();
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(editorApi.init({},mockRuntime)); app.use("/settings/user/keys", sshkeys.app());
}); });
after(function() {
})
beforeEach(function() { beforeEach(function() {
sinon.stub(mockRuntime.storage.projects.ssh, "listSSHKeys"); sinon.stub(mockRuntime.settings, "getUserKeys");
sinon.stub(mockRuntime.storage.projects.ssh, "getSSHKey"); sinon.stub(mockRuntime.settings, "getUserKey");
sinon.stub(mockRuntime.storage.projects.ssh, "generateSSHKey"); sinon.stub(mockRuntime.settings, "generateUserKey");
sinon.stub(mockRuntime.storage.projects.ssh, "deleteSSHKey"); sinon.stub(mockRuntime.settings, "removeUserKey");
}) })
afterEach(function() { afterEach(function() {
mockRuntime.storage.projects.ssh.listSSHKeys.restore(); mockRuntime.settings.getUserKeys.restore();
mockRuntime.storage.projects.ssh.getSSHKey.restore(); mockRuntime.settings.getUserKey.restore();
mockRuntime.storage.projects.ssh.generateSSHKey.restore(); mockRuntime.settings.generateUserKey.restore();
mockRuntime.storage.projects.ssh.deleteSSHKey.restore(); mockRuntime.settings.removeUserKey.restore();
}) })
it('GET /settings/user/keys --- return empty list', function(done) { it('GET /settings/user/keys --- return empty list', function(done) {
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve([])); mockRuntime.settings.getUserKeys.returns(Promise.resolve([]));
request(app) request(app)
.get("/settings/user/keys") .get("/settings/user/keys")
.expect(200) .expect(200)
@ -118,7 +77,7 @@ describe("api/editor/sshkeys", function() {
name: elem name: elem
}; };
}); });
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve(retList)); mockRuntime.settings.getUserKeys.returns(Promise.resolve(retList));
request(app) request(app)
.get("/settings/user/keys") .get("/settings/user/keys")
.expect(200) .expect(200)
@ -139,16 +98,16 @@ describe("api/editor/sshkeys", function() {
errInstance.code = "test_code"; errInstance.code = "test_code";
var p = Promise.reject(errInstance); var p = Promise.reject(errInstance);
p.catch(()=>{}); p.catch(()=>{});
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p); mockRuntime.settings.getUserKeys.returns(p);
request(app) request(app)
.get("/settings/user/keys") .get("/settings/user/keys")
.expect(400) .expect(500)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.have.property('error'); res.body.should.have.property('code');
res.body.error.should.be.equal(errInstance.code); res.body.code.should.be.equal(errInstance.code);
res.body.should.have.property('message'); res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message); res.body.message.should.be.equal(errInstance.message);
done(); done();
@ -156,7 +115,12 @@ describe("api/editor/sshkeys", function() {
}); });
it('GET /settings/user/keys/<key_file_name> --- return 404', function(done) { it('GET /settings/user/keys/<key_file_name> --- return 404', function(done) {
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(null)); var errInstance = new Error("Not Found.");
errInstance.code = "not_found";
errInstance.status = 404;
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.settings.getUserKey.returns(p);
request(app) request(app)
.get("/settings/user/keys/NOT_REAL") .get("/settings/user/keys/NOT_REAL")
.expect(404) .expect(404)
@ -168,19 +132,19 @@ describe("api/editor/sshkeys", function() {
}); });
}); });
it('GET /settings/user/keys --- return Unexpected Error', function(done) { it('GET /settings/user/keys --- return Unexpected Error', function(done) {
var errInstance = new Error("Messages....."); var errInstance = new Error();
var p = Promise.reject(errInstance); var p = Promise.reject(errInstance);
p.catch(()=>{}); p.catch(()=>{});
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p); mockRuntime.settings.getUserKeys.returns(p)
request(app) request(app)
.get("/settings/user/keys") .get("/settings/user/keys")
.expect(400) .expect(500)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.have.property('error'); res.body.should.have.property('code');
res.body.error.should.be.equal("unexpected_error"); res.body.code.should.be.equal("unexpected_error");
res.body.should.have.property('message'); res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString()); res.body.message.should.be.equal(errInstance.toString());
done(); done();
@ -190,7 +154,7 @@ describe("api/editor/sshkeys", function() {
it('GET /settings/user/keys/<key_file_name> --- return content', function(done) { it('GET /settings/user/keys/<key_file_name> --- return content', function(done) {
var key_file_name = "test_key"; var key_file_name = "test_key";
var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n"; var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n";
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(fileContent)); mockRuntime.settings.getUserKey.returns(Promise.resolve(fileContent));
request(app) request(app)
.get("/settings/user/keys/" + key_file_name) .get("/settings/user/keys/" + key_file_name)
.expect(200) .expect(200)
@ -198,7 +162,8 @@ describe("api/editor/sshkeys", function() {
if (err) { if (err) {
return done(err); return done(err);
} }
mockRuntime.storage.projects.ssh.getSSHKey.called.should.be.true(); mockRuntime.settings.getUserKey.called.should.be.true();
mockRuntime.settings.getUserKey.firstCall.args[0].should.eql({ user: undefined, id: 'test_key' });
res.body.should.be.deepEqual({ publickey: fileContent }); res.body.should.be.deepEqual({ publickey: fileContent });
done(); done();
}); });
@ -210,16 +175,16 @@ describe("api/editor/sshkeys", function() {
errInstance.code = "test_code"; errInstance.code = "test_code";
var p = Promise.reject(errInstance); var p = Promise.reject(errInstance);
p.catch(()=>{}); p.catch(()=>{});
mockRuntime.storage.projects.ssh.getSSHKey.returns(p); mockRuntime.settings.getUserKey.returns(p);
request(app) request(app)
.get("/settings/user/keys/" + key_file_name) .get("/settings/user/keys/" + key_file_name)
.expect(400) .expect(500)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.have.property('error'); res.body.should.have.property('code');
res.body.error.should.be.equal(errInstance.code); res.body.code.should.be.equal(errInstance.code);
res.body.should.have.property('message'); res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message); res.body.message.should.be.equal(errInstance.message);
done(); done();
@ -231,25 +196,25 @@ describe("api/editor/sshkeys", function() {
var errInstance = new Error("Messages....."); var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance); var p = Promise.reject(errInstance);
p.catch(()=>{}); p.catch(()=>{});
mockRuntime.storage.projects.ssh.getSSHKey.returns(p); mockRuntime.settings.getUserKey.returns(p);
request(app) request(app)
.get("/settings/user/keys/" + key_file_name) .get("/settings/user/keys/" + key_file_name)
.expect(400) .expect(500)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.have.property('error'); res.body.should.have.property('code');
res.body.error.should.be.equal("unexpected_error"); res.body.code.should.be.equal("unexpected_error");
res.body.should.have.property('message'); res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString()); res.body.message.should.be.equal("Messages.....");
done(); done();
}); });
}); });
it('POST /settings/user/keys --- success', function(done) { it('POST /settings/user/keys --- success', function(done) {
var key_file_name = "test_key"; var key_file_name = "test_key";
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name)); mockRuntime.settings.generateUserKey.returns(Promise.resolve(key_file_name));
request(app) request(app)
.post("/settings/user/keys") .post("/settings/user/keys")
.send({ name: key_file_name }) .send({ name: key_file_name })
@ -262,41 +227,23 @@ describe("api/editor/sshkeys", function() {
}); });
}); });
it('POST /settings/user/keys --- return parameter error', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name));
request(app)
.post("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("You need to have body or body.name");
done();
});
});
it('POST /settings/user/keys --- return Error', function(done) { it('POST /settings/user/keys --- return Error', function(done) {
var key_file_name = "test_key"; var key_file_name = "test_key";
var errInstance = new Error("Messages....."); var errInstance = new Error("Messages.....");
errInstance.code = "test_code"; errInstance.code = "test_code";
var p = Promise.reject(errInstance); var p = Promise.reject(errInstance);
p.catch(()=>{}); p.catch(()=>{});
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p); mockRuntime.settings.generateUserKey.returns(p);
request(app) request(app)
.post("/settings/user/keys") .post("/settings/user/keys")
.send({ name: key_file_name }) .send({ name: key_file_name })
.expect(400) .expect(500)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.have.property('error'); res.body.should.have.property('code');
res.body.error.should.be.equal("test_code"); res.body.code.should.be.equal("test_code");
res.body.should.have.property('message'); res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message); res.body.message.should.be.equal(errInstance.message);
done(); done();
@ -308,26 +255,26 @@ describe("api/editor/sshkeys", function() {
var errInstance = new Error("Messages....."); var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance); var p = Promise.reject(errInstance);
p.catch(()=>{}); p.catch(()=>{});
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p); mockRuntime.settings.generateUserKey.returns(p);
request(app) request(app)
.post("/settings/user/keys") .post("/settings/user/keys")
.send({ name: key_file_name }) .send({ name: key_file_name })
.expect(400) .expect(500)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.have.property('error'); res.body.should.have.property('code');
res.body.error.should.be.equal("unexpected_error"); res.body.code.should.be.equal("unexpected_error");
res.body.should.have.property('message'); res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString()); res.body.message.should.be.equal("Messages.....");
done(); done();
}); });
}); });
it('DELETE /settings/user/keys/<key_file_name> --- success', function(done) { it('DELETE /settings/user/keys/<key_file_name> --- success', function(done) {
var key_file_name = "test_key"; var key_file_name = "test_key";
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(Promise.resolve(true)); mockRuntime.settings.removeUserKey.returns(Promise.resolve(true));
request(app) request(app)
.delete("/settings/user/keys/" + key_file_name) .delete("/settings/user/keys/" + key_file_name)
.expect(204) .expect(204)
@ -346,16 +293,16 @@ describe("api/editor/sshkeys", function() {
errInstance.code = "test_code"; errInstance.code = "test_code";
var p = Promise.reject(errInstance); var p = Promise.reject(errInstance);
p.catch(()=>{}); p.catch(()=>{});
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p); mockRuntime.settings.removeUserKey.returns(p);
request(app) request(app)
.delete("/settings/user/keys/" + key_file_name) .delete("/settings/user/keys/" + key_file_name)
.expect(400) .expect(500)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.have.property('error'); res.body.should.have.property('code');
res.body.error.should.be.equal("test_code"); res.body.code.should.be.equal("test_code");
res.body.should.have.property('message'); res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message); res.body.message.should.be.equal(errInstance.message);
done(); done();
@ -367,18 +314,18 @@ describe("api/editor/sshkeys", function() {
var errInstance = new Error("Messages....."); var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance); var p = Promise.reject(errInstance);
p.catch(()=>{}); p.catch(()=>{});
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p); mockRuntime.settings.removeUserKey.returns(p);
request(app) request(app)
.delete("/settings/user/keys/" + key_file_name) .delete("/settings/user/keys/" + key_file_name)
.expect(400) .expect(500)
.end(function(err,res) { .end(function(err,res) {
if (err) { if (err) {
return done(err); return done(err);
} }
res.body.should.have.property('error'); res.body.should.have.property('code');
res.body.error.should.be.equal("unexpected_error"); res.body.code.should.be.equal("unexpected_error");
res.body.should.have.property('message'); res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString()); res.body.message.should.be.equal('Messages.....');
done(); done();
}); });
}); });

View File

@ -33,7 +33,7 @@ describe("api/editor/theme", function() {
fs.statSync.restore(); fs.statSync.restore();
}); });
it("applies the default theme", function() { it("applies the default theme", function() {
var result = theme.init({settings:{},version:function() { return '123.456'}}); var result = theme.init({});
should.not.exist(result); should.not.exist(result);
var context = theme.context(); var context = theme.context();
@ -43,13 +43,12 @@ describe("api/editor/theme", 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");
context.should.have.a.property("version","123.456");
should.not.exist(theme.settings()); should.not.exist(theme.settings());
}); });
it("picks up custom theme", function() { it("picks up custom theme", function() {
theme.init({settings:{ theme.init({
editorTheme: { editorTheme: {
page: { page: {
title: "Test Page Title", title: "Test Page Title",
@ -84,7 +83,7 @@ describe("api/editor/theme", function() {
image: "/absolute/path/to/login/page/big/image" // a 256x256 image image: "/absolute/path/to/login/page/big/image" // a 256x256 image
} }
} }
}}); });
theme.app(); theme.app();

View File

@ -20,8 +20,6 @@ var express = require("express");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var EventEmitter = require('events').EventEmitter;
var events = new EventEmitter();
var ui = require("../../../../red/api/editor/ui"); var ui = require("../../../../red/api/editor/ui");
@ -30,10 +28,13 @@ describe("api/editor/ui", function() {
before(function() { before(function() {
ui.init({ ui.init({
events:events,
nodes: { nodes: {
getNodeIconPath: function(module,icon) { getIcon: function(opts) {
return path.resolve(__dirname+'/../../../../public/icons/arrow-in.png'); return new Promise(function(resolve,reject) {
fs.readFile(path.resolve(__dirname+'/../../../../public/icons/arrow-in.png'), function(err,data) {
resolve(data);
})
});
} }
} }
}); });

View File

@ -23,7 +23,6 @@ var fs = require("fs");
var path = require("path"); var path = require("path");
var api = require("../../../red/api"); var api = require("../../../red/api");
var apiUtil = require("../../../red/api/util");
var apiAuth = require("../../../red/api/auth"); var apiAuth = require("../../../red/api/auth");
var apiEditor = require("../../../red/api/editor"); var apiEditor = require("../../../red/api/editor");
var apiAdmin = require("../../../red/api/admin"); var apiAdmin = require("../../../red/api/admin");
@ -31,7 +30,6 @@ var apiAdmin = require("../../../red/api/admin");
describe("api/index", function() { describe("api/index", function() {
var beforeEach = function() { var beforeEach = function() {
sinon.stub(apiUtil,"init",function(){});
sinon.stub(apiAuth,"init",function(){}); sinon.stub(apiAuth,"init",function(){});
sinon.stub(apiEditor,"init",function(){ sinon.stub(apiEditor,"init",function(){
var app = express(); var app = express();
@ -48,7 +46,6 @@ describe("api/index", function() {
}); });
}; };
var afterEach = function() { var afterEach = function() {
apiUtil.init.restore();
apiAuth.init.restore(); apiAuth.init.restore();
apiAuth.login.restore(); apiAuth.login.restore();
apiEditor.init.restore(); apiEditor.init.restore();
@ -59,18 +56,14 @@ describe("api/index", function() {
afterEach(afterEach); afterEach(afterEach);
it("does not setup admin api if httpAdminRoot is false", function(done) { it("does not setup admin api if httpAdminRoot is false", function(done) {
api.init({},{ api.init({},{ httpAdminRoot: false },{},{});
settings: { httpAdminRoot: false }
});
should.not.exist(api.adminApp); should.not.exist(api.adminApp);
done(); done();
}); });
describe('initalises admin api without adminAuth', function(done) { describe('initalises admin api without adminAuth', function(done) {
before(function() { before(function() {
beforeEach(); beforeEach();
api.init({},{ api.init({},{},{},{});
settings: { }
});
}); });
after(afterEach); after(afterEach);
it('exposes the editor',function(done) { it('exposes the editor',function(done) {
@ -87,9 +80,7 @@ describe("api/index", function() {
describe('initalises admin api without editor', function(done) { describe('initalises admin api without editor', function(done) {
before(function() { before(function() {
beforeEach(); beforeEach();
api.init({},{ api.init({},{ disableEditor: true },{},{});
settings: { disableEditor: true }
});
}); });
after(afterEach); after(afterEach);
it('does not expose the editor',function(done) { it('does not expose the editor',function(done) {

View File

@ -15,11 +15,17 @@
**/ **/
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 apiUtil = require("../../../red/api/util"); var apiUtil = require("../../../red/api/util");
var log = require("../../../red/util").log; // TODO: separate module
var i18n = require("../../../red/util").i18n; // TODO: separate module
describe("api/util", function() { describe("api/util", function() {
describe("errorHandler", function() { describe("errorHandler", function() {
var loggedError = null; var loggedError = null;
@ -27,17 +33,8 @@ describe("api/util", function() {
var app; var app;
before(function() { before(function() {
app = express(); app = express();
apiUtil.init({ sinon.stub(log,'error',function(msg) {loggedError = msg;});
log:{ sinon.stub(log,'audit',function(event) {loggedEvent = event;});
error: function(msg) {
loggedError = msg;
},
audit: function(event) {
loggedEvent = event;
}
},
i18n:{}
})
app.get("/tooLarge", function(req,res) { app.get("/tooLarge", function(req,res) {
var err = new Error(); var err = new Error();
err.message = "request entity too large"; err.message = "request entity too large";
@ -49,6 +46,10 @@ describe("api/util", function() {
throw err; throw err;
},apiUtil.errorHandler) },apiUtil.errorHandler)
}); });
after(function() {
log.error.restore();
log.audit.restore();
})
beforeEach(function() { beforeEach(function() {
loggedError = null; loggedError = null;
loggedEvent = null; loggedEvent = null;
@ -91,11 +92,13 @@ describe("api/util", function() {
}) })
describe('determineLangFromHeaders', function() { describe('determineLangFromHeaders', function() {
var oldDefaultLang;
before(function() { before(function() {
apiUtil.init({ oldDefaultLang = i18n.defaultLang;
log:{}, i18n.defaultLang = "en-US";
i18n:{defaultLang:"en-US"} })
}); after(function() {
i18n.defaultLang = oldDefaultLang;
}) })
it('returns the default lang if non provided', function() { it('returns the default lang if non provided', function() {
apiUtil.determineLangFromHeaders(null).should.eql("en-US"); apiUtil.determineLangFromHeaders(null).should.eql("en-US");

View File

@ -14,6 +14,229 @@
* limitations under the License. * limitations under the License.
**/ **/
var should = require("should");
var sinon = require("sinon");
var comms = require("../../../red/runtime-api/comms");
describe("runtime-api/comms", function() { describe("runtime-api/comms", function() {
it.skip('more tests needed', function(){}) describe("listens for events", function() {
var messages = [];
var clientConnection = {
send: function(topic,data) {
messages.push({topic,data})
}
}
var eventHandlers = {};
before(function(done) {
comms.init({
log: {
trace: function(){}
},
events: {
removeListener: function() {},
on: function(evt,handler) {
eventHandlers[evt] = handler;
}
}
})
comms.addConnection({client: clientConnection}).then(done);
})
after(function(done) {
comms.removeConnection({client: clientConnection}).then(done);
})
afterEach(function() {
messages = [];
})
it('runtime events',function(){
eventHandlers.should.have.property('runtime-event');
eventHandlers['runtime-event']({
id: "my-event",
payload: "my-payload"
})
messages.should.have.length(1);
messages[0].should.have.property("topic","notification/my-event");
messages[0].should.have.property("data","my-payload")
})
it('status events',function(){
eventHandlers.should.have.property('node-status');
eventHandlers['node-status']({
id: "my-event",
status: "my-status"
})
messages.should.have.length(1);
messages[0].should.have.property("topic","status/my-event");
messages[0].should.have.property("data","my-status")
})
it('comms events',function(){
eventHandlers.should.have.property('runtime-event');
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(1);
messages[0].should.have.property("topic","my-topic");
messages[0].should.have.property("data","my-payload")
})
});
describe("manages connections", function() {
var eventHandlers = {};
var messages = [];
var clientConnection1 = {
send: function(topic,data) {
messages.push({topic,data})
}
}
var clientConnection2 = {
send: function(topic,data) {
messages.push({topic,data})
}
}
before(function() {
comms.init({
log: {
trace: function(){}
},
events: {
removeListener: function() {},
on: function(evt,handler) {
eventHandlers[evt] = handler;
}
}
})
})
afterEach(function(done) {
comms.removeConnection({client: clientConnection1}).then(function() {
comms.removeConnection({client: clientConnection2}).then(done);
});
messages = [];
})
it('adds new connections',function(done){
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection1}).then(function() {
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(1);
comms.addConnection({client: clientConnection2}).then(function() {
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(3);
done();
}).catch(done);
});
});
it('removes connections',function(done){
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection1}).then(function() {
comms.addConnection({client: clientConnection2}).then(function() {
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(2);
comms.removeConnection({client: clientConnection1}).then(function() {
eventHandlers['comms']({
topic: "my-topic",
data: "my-payload"
})
messages.should.have.length(3);
done();
});
}).catch(done);
});
})
})
describe("subscriptions", function() {
var messages = [];
var clientConnection = {
send: function(topic,data) {
messages.push({topic,data})
}
}
var clientConnection2 = {
send: function(topic,data) {
messages.push({topic,data})
}
}
var eventHandlers = {};
before(function() {
comms.init({
log: {
trace: function(){}
},
events: {
removeListener: function() {},
on: function(evt,handler) {
eventHandlers[evt] = handler;
}
}
})
})
afterEach(function(done) {
messages = [];
comms.removeConnection({client: clientConnection}).then(done);
})
it('subscribe triggers retained messages',function(done){
eventHandlers['comms']({
topic: "my-event",
data: "my-payload",
retain: true
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection}).then(function() {
return comms.subscribe({client: clientConnection, topic: "my-event"}).then(function() {
messages.should.have.length(1);
messages[0].should.have.property("topic","my-event");
messages[0].should.have.property("data","my-payload");
done();
});
}).catch(done);
})
it('retained messages get cleared',function(done) {
eventHandlers['comms']({
topic: "my-event",
data: "my-payload",
retain: true
})
messages.should.have.length(0);
comms.addConnection({client: clientConnection}).then(function() {
return comms.subscribe({client: clientConnection, topic: "my-event"}).then(function() {
messages.should.have.length(1);
messages[0].should.have.property("topic","my-event");
messages[0].should.have.property("data","my-payload");
// Now we have a retained message, clear it
eventHandlers['comms']({
topic: "my-event",
data: "my-payload-cleared"
});
messages.should.have.length(2);
messages[1].should.have.property("topic","my-event");
messages[1].should.have.property("data","my-payload-cleared");
// Now add a second client and subscribe - no message should arrive
return comms.addConnection({client: clientConnection2}).then(function() {
return comms.subscribe({client: clientConnection2, topic: "my-event"}).then(function() {
messages.should.have.length(2);
done();
});
});
});
}).catch(done);
});
})
}); });

View File

@ -17,3 +17,275 @@
describe("runtime-api/flows", function() { describe("runtime-api/flows", function() {
it.skip('more tests needed', function(){}) it.skip('more tests needed', function(){})
}); });
/*
var should = require("should");
var request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');
var sinon = require('sinon');
var when = require('when');
var flows = require("../../../../red/api/admin/flows");
describe("api/admin/flows", function() {
var app;
before(function() {
app = express();
app.use(bodyParser.json());
app.get("/flows",flows.get);
app.post("/flows",flows.post);
});
it('returns flow - v1', function(done) {
flows.init({
settings: {},
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
}
});
request(app)
.get('/flows')
.set('Accept', 'application/json')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.lengthOf(3);
done();
} catch(e) {
return done(e);
}
});
});
it('returns flow - v2', function(done) {
flows.init({
settings: {},
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
}
});
request(app)
.get('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.a.property('rev','123');
res.body.should.have.a.property('flows');
res.body.flows.should.have.lengthOf(3);
done();
} catch(e) {
return done(e);
}
});
});
it('returns flow - bad version', function(done) {
request(app)
.get('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','xxx')
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.a.property('code','invalid_api_version');
done();
} catch(e) {
return done(e);
}
});
});
it('sets flows - default - v1', function(done) {
var setFlows = sinon.spy(function() { return when.resolve();});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
setFlows.calledOnce.should.be.true();
setFlows.lastCall.args[1].should.eql('full');
done();
});
});
it('sets flows - non-default - v1', function(done) {
var setFlows = sinon.spy(function() { return when.resolve();});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-Deployment-Type','nodes')
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
setFlows.calledOnce.should.be.true();
setFlows.lastCall.args[1].should.eql('nodes');
done();
});
});
it('set flows - rejects mismatched revision - v2', function(done) {
var setFlows = sinon.spy(function() { return when.resolve();});
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows,
getFlows: getFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.send({rev:456,flows:[4,5,6]})
.expect(409)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("code","version_mismatch");
done();
});
});
it('set flows - rev provided - v2', function(done) {
var setFlows = sinon.spy(function() { return when.resolve(456);});
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows,
getFlows: getFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.send({rev:123,flows:[4,5,6]})
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("rev",456);
done();
});
});
it('set flows - no rev provided - v2', function(done) {
var setFlows = sinon.spy(function() { return when.resolve(456);});
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: setFlows,
getFlows: getFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','v2')
.send({flows:[4,5,6]})
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("rev",456);
done();
});
});
it('sets flow - bad version', function(done) {
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-API-Version','xxx')
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
try {
res.body.should.have.a.property('code','invalid_api_version');
done();
} catch(e) {
return done(e);
}
});
});
it('reloads flows', function(done) {
var loadFlows = sinon.spy(function() { return when.resolve(); });
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
loadFlows: loadFlows
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.set('Node-RED-Deployment-Type','reload')
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
loadFlows.called.should.be.true();
done();
});
});
it('returns error when set fails', function(done) {
flows.init({
log:{warn:function(){},_:function(){},audit:function(){}},
nodes:{
setFlows: function() { return when.reject(new Error("expected error")); }
}
});
request(app)
.post('/flows')
.set('Accept', 'application/json')
.expect(500)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("message","expected error");
done();
});
});
});
*/

View File

@ -14,6 +14,41 @@
* limitations under the License. * limitations under the License.
**/ **/
var should = require("should");
var sinon = require("sinon");
var index = require("../../../red/runtime-api/index");
describe("runtime-api/index", function() { describe("runtime-api/index", function() {
it.skip('more tests needed', function(){}) before(function() {
["comms","flows","nodes","settings","library","projects"].forEach(n => {
sinon.stub(require(`../../../red/runtime-api/${n}`),"init",()=>{});
})
});
after(function() {
["comms","flows","nodes","settings","library","projects"].forEach(n => {
require(`../../../red/runtime-api/${n}`).init.restore()
})
})
it('isStarted', function(done) {
index.init({
isStarted: ()=>true
});
index.isStarted({}).then(function(started) {
started.should.be.true();
done();
}).catch(done);
})
it('isStarted', function(done) {
index.init({
version: ()=>"1.2.3.4"
});
index.version({}).then(function(version) {
version.should.eql("1.2.3.4");
done();
}).catch(done);
})
}); });

View File

@ -17,3 +17,342 @@
describe("runtime-api/library", function() { describe("runtime-api/library", function() {
it.skip('more tests needed', function(){}) it.skip('more tests needed', function(){})
}); });
/*
var should = require("should");
var sinon = require("sinon");
var fs = require("fs");
var fspath = require('path');
var request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');
var when = require('when');
var app;
var library = require("../../../../red/api/editor/library");
var auth = require("../../../../red/api/auth");
describe("api/editor/library", function() {
function initLibrary(_flows,_libraryEntries,_examples,_exampleFlowPathFunction) {
var flows = _flows;
var libraryEntries = _libraryEntries;
library.init(app,{
log:{audit:function(){},_:function(){},warn:function(){}},
storage: {
init: function() {
return when.resolve();
},
getAllFlows: function() {
return when.resolve(flows);
},
getFlow: function(fn) {
if (flows[fn]) {
return when.resolve(flows[fn]);
} else if (fn.indexOf("..")!==-1) {
var err = new Error();
err.code = 'forbidden';
return when.reject(err);
} else {
return when.reject();
}
},
saveFlow: function(fn,data) {
if (fn.indexOf("..")!==-1) {
var err = new Error();
err.code = 'forbidden';
return when.reject(err);
}
flows[fn] = data;
return when.resolve();
},
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]) {
return when.resolve(libraryEntries[type][path]);
} else {
return when.reject();
}
},
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;
return when.resolve();
}
},
events: {
on: function(){},
removeListener: function(){}
},
nodes: {
getNodeExampleFlows: function() {
return _examples;
},
getNodeExampleFlowPath: _exampleFlowPathFunction
}
});
}
describe("flows", function() {
before(function() {
app = express();
app.use(bodyParser.json());
app.get("/library/flows",library.getAll);
app.post(new RegExp("/library/flows\/(.*)"),library.post);
app.get(new RegExp("/library/flows\/(.*)"),library.get);
app.response.sendFile = function (path) {
app.response.json.call(this, {sendFile: path});
};
sinon.stub(fs,"statSync",function() { return true; });
});
after(function() {
fs.statSync.restore();
});
it('returns empty result', function(done) {
initLibrary({},{flows:{}});
request(app)
.get('/library/flows')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.not.have.property('f');
res.body.should.not.have.property('d');
done();
});
});
it('returns 404 for non-existent entry', function(done) {
initLibrary({},{flows:{}});
request(app)
.get('/library/flows/foo')
.expect(404)
.end(done);
});
it('can store and retrieve item', function(done) {
initLibrary({},{flows:{}});
var flow = '[]';
request(app)
.post('/library/flows/foo')
.set('Content-Type', 'application/json')
.send(flow)
.expect(204).end(function (err, res) {
if (err) {
throw err;
}
request(app)
.get('/library/flows/foo')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.text.should.equal(flow);
done();
});
});
});
it('lists a stored item', function(done) {
initLibrary({f:["bar"]});
request(app)
.get('/library/flows')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property('f');
should.deepEqual(res.body.f,['bar']);
done();
});
});
it('returns 403 for malicious get attempt', function(done) {
initLibrary({});
// without the userDir override the malicious url would be
// http://127.0.0.1:1880/library/flows/../../package to
// obtain package.json from the node-red root.
request(app)
.get('/library/flows/../../../../../package')
.expect(403)
.end(done);
});
it('returns 403 for malicious post attempt', function(done) {
initLibrary({});
// without the userDir override the malicious url would be
// http://127.0.0.1:1880/library/flows/../../package to
// obtain package.json from the node-red root.
request(app)
.post('/library/flows/../../../../../package')
.expect(403)
.end(done);
});
it('includes examples flows if set', function(done) {
var examples = {"d":{"node-module":{"f":["example-one"]}}};
initLibrary({},{},examples);
request(app)
.get('/library/flows')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property('d');
res.body.d.should.have.property('_examples_');
should.deepEqual(res.body.d._examples_,examples);
done();
});
});
it('can retrieve an example flow', function(done) {
var examples = {"d":{"node-module":{"f":["example-one"]}}};
initLibrary({},{},examples,function(module,path) {
return module + ':' + path
});
request(app)
.get('/library/flows/_examples_/node-module/example-one')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property('sendFile',
'node-module:example-one');
done();
});
});
it('can retrieve an example flow in an org scoped package', function(done) {
var examples = {"d":{"@org_scope/node_package":{"f":["example-one"]}}};
initLibrary({},{},examples,function(module,path) {
return module + ':' + path
});
request(app)
.get('/library/flows/_examples_/@org_scope/node_package/example-one')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property('sendFile',
'@org_scope/node_package:example-one');
done();
});
});
});
describe("type", function() {
before(function() {
app = express();
app.use(bodyParser.json());
initLibrary({},{});
auth.init({settings:{}});
library.register("test");
});
it('returns empty result', function(done) {
initLibrary({},{'test':{"":[]}});
request(app)
.get('/library/test')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.not.have.property('f');
done();
});
});
it('returns 404 for non-existent entry', function(done) {
initLibrary({},{});
request(app)
.get('/library/test/foo')
.expect(404)
.end(done);
});
it('can store and retrieve item', function(done) {
initLibrary({},{'test':{}});
var flow = {text:"test content"};
request(app)
.post('/library/test/foo')
.set('Content-Type', 'application/json')
.send(flow)
.expect(204).end(function (err, res) {
if (err) {
throw err;
}
request(app)
.get('/library/test/foo')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.text.should.equal(flow.text);
done();
});
});
});
it('lists a stored item', function(done) {
initLibrary({},{'test':{'a':['abc','def']}});
request(app)
.get('/library/test/a')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
// This response isn't strictly accurate - but it
// verifies the api returns what storage gave it
should.deepEqual(res.body,['abc','def']);
done();
});
});
it('returns 403 for malicious access attempt', function(done) {
request(app)
.get('/library/test/../../../../../../../../../../etc/passwd')
.expect(403)
.end(done);
});
it('returns 403 for malicious access attempt', function(done) {
request(app)
.get('/library/test/..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd')
.expect(403)
.end(done);
});
it('returns 403 for malicious access attempt', function(done) {
request(app)
.post('/library/test/../../../../../../../../../../etc/passwd')
.set('Content-Type', 'text/plain')
.send('root:x:0:0:root:/root:/usr/bin/tclsh')
.expect(403)
.end(done);
});
});
});
*/

View File

@ -17,3 +17,829 @@
describe("runtime-api/nodes", function() { describe("runtime-api/nodes", function() {
it.skip('more tests needed', function(){}) it.skip('more tests needed', function(){})
}); });
/*
var should = require("should");
var request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');
var sinon = require('sinon');
var when = require('when');
var nodes = require("../../../../red/api/admin/nodes");
var apiUtil = require("../../../../red/api/util");
describe("api/admin/nodes", function() {
var app;
function initNodes(runtime) {
runtime.log = {
audit:function(e){},//console.log(e)},
_:function(){},
info: function(){},
warn: function(){}
}
runtime.events = {
emit: function(){}
}
nodes.init(runtime);
}
before(function() {
app = express();
app.use(bodyParser.json());
app.get("/nodes",nodes.getAll);
app.post("/nodes",nodes.post);
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.getModule);
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.getSet);
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.putModule);
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.putSet);
app.get("/getIcons",nodes.getIcons);
app.delete("/nodes/:id",nodes.delete);
sinon.stub(apiUtil,"determineLangFromHeaders", function() {
return "en-US";
});
});
after(function() {
apiUtil.determineLangFromHeaders.restore();
})
describe('get nodes', function() {
it('returns node list', function(done) {
initNodes({
nodes:{
getNodeList: function() {
return [1,2,3];
}
}
});
request(app)
.get('/nodes')
.set('Accept', 'application/json')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.be.an.Array();
res.body.should.have.lengthOf(3);
done();
});
});
it('returns node configs', function(done) {
initNodes({
nodes:{
getNodeConfigs: function() {
return "<script></script>";
}
},
i18n: {
determineLangFromHeaders: function(){}
}
});
request(app)
.get('/nodes')
.set('Accept', 'text/html')
.expect(200)
.expect("<script></script>")
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns node module info', function(done) {
initNodes({
nodes:{
getModuleInfo: function(id) {
return {"node-red":{name:"node-red"}}[id];
}
}
});
request(app)
.get('/nodes/node-red')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("name","node-red");
done();
});
});
it('returns 404 for unknown module', function(done) {
initNodes({
nodes:{
getModuleInfo: function(id) {
return {"node-red":{name:"node-red"}}[id];
}
}
});
request(app)
.get('/nodes/node-blue')
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns individual node info', function(done) {
initNodes({
nodes:{
getNodeInfo: function(id) {
return {"node-red/123":{id:"node-red/123"}}[id];
}
}
});
request(app)
.get('/nodes/node-red/123')
.set('Accept', 'application/json')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("id","node-red/123");
done();
});
});
it('returns individual node configs', function(done) {
initNodes({
nodes:{
getNodeConfig: function(id) {
return {"node-red/123":"<script></script>"}[id];
}
},
i18n: {
determineLangFromHeaders: function(){}
}
});
request(app)
.get('/nodes/node-red/123')
.set('Accept', 'text/html')
.expect(200)
.expect("<script></script>")
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns 404 for unknown node', function(done) {
initNodes({
nodes:{
getNodeInfo: function(id) {
return {"node-red/123":{id:"node-red/123"}}[id];
}
}
});
request(app)
.get('/nodes/node-red/456')
.set('Accept', 'application/json')
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
});
describe('install', function() {
it('returns 400 if settings are unavailable', function(done) {
initNodes({
settings:{available:function(){return false}}
});
request(app)
.post('/nodes')
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns 400 if request is invalid', function(done) {
initNodes({
settings:{available:function(){return true}}
});
request(app)
.post('/nodes')
.send({})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
describe('by module', function() {
it('installs the module and returns module info', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return null; },
installModule: function() {
return when.resolve({
name:"foo",
nodes:[{id:"123"}]
});
}
}
});
request(app)
.post('/nodes')
.send({module: 'foo'})
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("name","foo");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("id","123");
done();
});
});
it('fails the install if already installed', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return {nodes:{id:"123"}}; },
installModule: function() {
return when.resolve({id:"123"});
}
}
});
request(app)
.post('/nodes')
.send({module: 'foo'})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('fails the install if module error', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return null },
installModule: function() {
return when.reject(new Error("test error"));
}
}
});
request(app)
.post('/nodes')
.send({module: 'foo'})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("message","Error: test error");
done();
});
});
it('fails the install if module not found', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return null },
installModule: function() {
var err = new Error("test error");
err.code = 404;
return when.reject(err);
}
}
});
request(app)
.post('/nodes')
.send({module: 'foo'})
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
});
});
describe('delete', function() {
it('returns 400 if settings are unavailable', function(done) {
initNodes({
settings:{available:function(){return false}}
});
request(app)
.del('/nodes/123')
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
describe('by module', function() {
it('uninstalls the module', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return {nodes:[{id:"123"}]} },
getNodeInfo: function() { return null },
uninstallModule: function() { return when.resolve({id:"123"});}
}
});
request(app)
.del('/nodes/foo')
.expect(204)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('fails the uninstall if the module is not installed', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return null },
getNodeInfo: function() { return null }
}
});
request(app)
.del('/nodes/foo')
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('fails the uninstall if the module is not installed', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return {nodes:[{id:"123"}]} },
getNodeInfo: function() { return null },
uninstallModule: function() { return when.reject(new Error("test error"));}
}
});
request(app)
.del('/nodes/foo')
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("message","Error: test error");
done();
});
});
});
});
describe('enable/disable', function() {
it('returns 400 if settings are unavailable', function(done) {
initNodes({
settings:{available:function(){return false}}
});
request(app)
.put('/nodes/123')
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns 400 for invalid node payload', function(done) {
initNodes({
settings:{available:function(){return true}}
});
request(app)
.put('/nodes/node-red/foo')
.send({})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("message","Invalid request");
done();
});
});
it('returns 400 for invalid module payload', function(done) {
initNodes({
settings:{available:function(){return true}}
});
request(app)
.put('/nodes/foo')
.send({})
.expect(400)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("message","Invalid request");
done();
});
});
it('returns 404 for unknown node', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getNodeInfo: function() { return null }
}
});
request(app)
.put('/nodes/node-red/foo')
.send({enabled:false})
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('returns 404 for unknown module', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function(id) { return null }
}
});
request(app)
.put('/nodes/node-blue')
.send({enabled:false})
.expect(404)
.end(function(err,res) {
if (err) {
throw err;
}
done();
});
});
it('enables disabled node', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getNodeInfo: function() { return {id:"123",enabled: false} },
enableNode: function() { return when.resolve({id:"123",enabled: true,types:['a']}); }
}
});
request(app)
.put('/nodes/node-red/foo')
.send({enabled:true})
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("id","123");
res.body.should.have.property("enabled",true);
done();
});
});
it('disables enabled node', function(done) {
initNodes({
settings:{available:function(){return true}},
nodes:{
getNodeInfo: function() { return {id:"123",enabled: true} },
disableNode: function() { return when.resolve({id:"123",enabled: false,types:['a']}); }
}
});
request(app)
.put('/nodes/node-red/foo')
.send({enabled:false})
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("id","123");
res.body.should.have.property("enabled",false);
done();
});
});
describe('no-ops if already in the right state', function() {
function run(state,done) {
var enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) });
var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) });
initNodes({
settings:{available:function(){return true}},
nodes:{
getNodeInfo: function() { return {id:"123",enabled: state} },
enableNode: enableNode,
disableNode: disableNode
}
});
request(app)
.put('/nodes/node-red/foo')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
if (err) {
throw err;
}
enableNodeCalled.should.be.false();
disableNodeCalled.should.be.false();
res.body.should.have.property("id","123");
res.body.should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
describe('does not no-op if err on node', function() {
function run(state,done) {
var enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) });
var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) });
initNodes({
settings:{available:function(){return true}},
nodes:{
getNodeInfo: function() { return {id:"123",enabled: state, err:"foo"} },
enableNode: enableNode,
disableNode: disableNode
}
});
request(app)
.put('/nodes/node-red/foo')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
if (err) {
throw err;
}
enableNodeCalled.should.be.equal(state);
disableNodeCalled.should.be.equal(!state);
res.body.should.have.property("id","123");
res.body.should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
it('enables disabled module', function(done) {
var n1 = {id:"123",enabled:false,types:['a']};
var n2 = {id:"456",enabled:false,types:['b']};
var enableNode = sinon.stub();
enableNode.onFirstCall().returns((function() {
n1.enabled = true;
return when.resolve(n1);
})());
enableNode.onSecondCall().returns((function() {
n2.enabled = true;
return when.resolve(n2);
})());
enableNode.returns(null);
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function() { return {name:"node-red", nodes:[n1, n2]} },
enableNode: enableNode
}
});
request(app)
.put('/nodes/node-red')
.send({enabled:true})
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",true);
res.body.nodes[1].should.have.property("enabled",true);
done();
});
});
it('disables enabled module', function(done) {
var n1 = {id:"123",enabled:true,types:['a']};
var n2 = {id:"456",enabled:true,types:['b']};
var disableNode = sinon.stub();
disableNode.onFirstCall().returns((function() {
n1.enabled = false;
return when.resolve(n1);
})());
disableNode.onSecondCall().returns((function() {
n2.enabled = false;
return when.resolve(n2);
})());
disableNode.returns(null);
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function() { return {name:"node-red", nodes:[n1, n2]} },
disableNode: disableNode
}
});
request(app)
.put('/nodes/node-red')
.send({enabled:false})
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",false);
res.body.nodes[1].should.have.property("enabled",false);
done();
});
});
describe('no-ops if a node in module already in the right state', function() {
function run(state,done) {
var node = {id:"123",enabled:state,types:['a']};
var enableNode = sinon.spy(function(id) {
node.enabled = true;
return when.resolve(node);
});
var disableNode = sinon.spy(function(id) {
node.enabled = false;
return when.resolve(node);
});
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function() { return {name:"node-red", nodes:[node]}; },
enableNode: enableNode,
disableNode: disableNode
}
});
request(app)
.put('/nodes/node-red')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
if (err) {
throw err;
}
enableNodeCalled.should.be.false();
disableNodeCalled.should.be.false();
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
describe('does not no-op if err on a node in module', function() {
function run(state,done) {
var node = {id:"123",enabled:state,types:['a'],err:"foo"};
var enableNode = sinon.spy(function(id) {
node.enabled = true;
return when.resolve(node);
});
var disableNode = sinon.spy(function(id) {
node.enabled = false;
return when.resolve(node);
});
initNodes({
settings:{available:function(){return true}},
nodes:{
getModuleInfo: function() { return {name:"node-red", nodes:[node]}; },
enableNode: enableNode,
disableNode: disableNode
}
});
request(app)
.put('/nodes/node-red')
.send({enabled:state})
.expect(200)
.end(function(err,res) {
var enableNodeCalled = enableNode.called;
var disableNodeCalled = disableNode.called;
if (err) {
throw err;
}
enableNodeCalled.should.be.equal(state);
disableNodeCalled.should.be.equal(!state);
res.body.should.have.property("name","node-red");
res.body.should.have.property("nodes");
res.body.nodes[0].should.have.property("enabled",state);
done();
});
}
it('already enabled', function(done) {
run(true,done);
});
it('already disabled', function(done) {
run(false,done);
});
});
});
describe('get icons', function() {
it('returns icon list', function(done) {
initNodes({
nodes:{
getNodeIcons: function() {
return {"module":["1.png","2.png","3.png"]};
}
}
});
request(app)
.get('/getIcons')
.expect(200)
.end(function(err,res) {
if (err) {
throw err;
}
console.log(res.body);
res.body.should.have.property("module");
res.body.module.should.be.an.Array();
res.body.module.should.have.lengthOf(3);
done();
});
});
});
});
*/

View File

@ -17,3 +17,569 @@
describe("runtime-api/settings", function() { describe("runtime-api/settings", function() {
it.skip('more tests needed', function(){}) it.skip('more tests needed', function(){})
}); });
/*
it('returns the filtered settings', function(done) {
info.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function(obj) {
obj.testNodeSetting = "helloWorld";
}
},
nodes: {
paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
res.body.should.have.property("version","testVersion");
res.body.should.have.property("paletteCategories",["red","blue","green"]);
res.body.should.have.property("editorTheme",{test:456});
res.body.should.have.property("testNodeSetting","helloWorld");
res.body.should.not.have.property("foo",123);
res.body.should.have.property("flowEncryptionType","test-key-type");
done();
});
});
it('includes project settings if projects available', function(done) {
info.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function(obj) {
obj.testNodeSetting = "helloWorld";
}
},
nodes: {
paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {
projects: {
getActiveProject: () => 'test-active-project',
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("project","test-active-project");
res.body.should.not.have.property("files");
res.body.should.have.property("git");
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
done();
});
});
it('includes existing files details if projects enabled but no active project and files exist', function(done) {
info.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function(obj) {
obj.testNodeSetting = "helloWorld";
}
},
nodes: {
paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {
projects: {
flowFileExists: () => true,
getActiveProject: () => false,
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.not.have.property("project");
res.body.should.have.property("files");
res.body.files.should.have.property("flow",'test-flow-file');
res.body.files.should.have.property("credentials",'test-creds-file');
res.body.should.have.property("git");
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
done();
});
});
it('does not include file details if projects enabled but no active project and files do not exist', function(done) {
info.init({
settings: {
foo: 123,
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function(obj) {
obj.testNodeSetting = "helloWorld";
}
},
nodes: {
paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {
projects: {
flowFileExists: () => false,
getActiveProject: () => false,
getFlowFilename: () => 'test-flow-file',
getCredentialsFilename: () => 'test-creds-file',
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
}
}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.not.have.property("project");
res.body.should.not.have.property("files");
res.body.should.have.property("git");
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
done();
});
});
it('overrides palette editable if runtime says it is disabled', function(done) {
info.init({
settings: {
httpNodeRoot: "testHttpNodeRoot",
version: "testVersion",
paletteCategories :["red","blue","green"],
exportNodeSettings: function() {}
},
nodes: {
paletteEditorEnabled: function() { return false; },
getCredentialKeyType: function() { return "test-key-type"}
},
log: { error: console.error },
storage: {}
});
request(app)
.get("/settings")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
res.body.should.have.property("version","testVersion");
res.body.should.have.property("paletteCategories",["red","blue","green"]);
res.body.should.have.property("editorTheme");
res.body.editorTheme.should.have.property("test",456);
res.body.editorTheme.should.have.property("palette",{editable:false});
done();
});
})
*/
/*
var should = require("should");
var sinon = require("sinon");
var request = require("supertest");
var express = require("express");
var editorApi = require("../../../../red/api/editor");
var comms = require("../../../../red/api/editor/comms");
var info = require("../../../../red/api/editor/settings");
var auth = require("../../../../red/api/auth");
var sshkeys = require("../../../../red/api/editor/sshkeys");
var when = require("when");
var bodyParser = require("body-parser");
var fs = require("fs-extra");
var fspath = require("path");
describe("api/editor/sshkeys", function() {
var app;
var mockList = [
'library','theme','locales','credentials','comms'
]
var isStarted = true;
var errors = [];
var session_data = {};
var mockRuntime = {
settings:{
httpNodeRoot: true,
httpAdminRoot: true,
disableEditor: false,
exportNodeSettings:function(){},
storage: {
getSessions: function(){
return when.resolve(session_data);
},
setSessions: function(_session) {
session_data = _session;
return when.resolve();
}
}
},
log:{audit:function(){},error:function(msg){errors.push(msg)},trace:function(){}},
storage: {
projects: {
ssh: {
init: function(){},
listSSHKeys: function(){},
getSSHKey: function(){},
generateSSHKey: function(){},
deleteSSHKey: function(){}
}
}
},
events:{on:function(){},removeListener:function(){}},
isStarted: function() { return isStarted; },
nodes: {paletteEditorEnabled: function() { return false }}
};
before(function() {
auth.init(mockRuntime);
app = express();
app.use(bodyParser.json());
app.use(editorApi.init({},mockRuntime));
});
after(function() {
})
beforeEach(function() {
sinon.stub(mockRuntime.storage.projects.ssh, "listSSHKeys");
sinon.stub(mockRuntime.storage.projects.ssh, "getSSHKey");
sinon.stub(mockRuntime.storage.projects.ssh, "generateSSHKey");
sinon.stub(mockRuntime.storage.projects.ssh, "deleteSSHKey");
})
afterEach(function() {
mockRuntime.storage.projects.ssh.listSSHKeys.restore();
mockRuntime.storage.projects.ssh.getSSHKey.restore();
mockRuntime.storage.projects.ssh.generateSSHKey.restore();
mockRuntime.storage.projects.ssh.deleteSSHKey.restore();
})
it('GET /settings/user/keys --- return empty list', function(done) {
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve([]));
request(app)
.get("/settings/user/keys")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('keys');
res.body.keys.should.be.empty();
done();
});
});
it('GET /settings/user/keys --- return normal list', function(done) {
var fileList = [
'test_key01',
'test_key02'
];
var retList = fileList.map(function(elem) {
return {
name: elem
};
});
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve(retList));
request(app)
.get("/settings/user/keys")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('keys');
for (var item of retList) {
res.body.keys.should.containEql(item);
}
done();
});
});
it('GET /settings/user/keys --- return Error', function(done) {
var errInstance = new Error("Messages here.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p);
request(app)
.get("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal(errInstance.code);
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return 404', function(done) {
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(null));
request(app)
.get("/settings/user/keys/NOT_REAL")
.expect(404)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
it('GET /settings/user/keys --- return Unexpected Error', function(done) {
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p);
request(app)
.get("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return content', function(done) {
var key_file_name = "test_key";
var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n";
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(fileContent));
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
mockRuntime.storage.projects.ssh.getSSHKey.called.should.be.true();
res.body.should.be.deepEqual({ publickey: fileContent });
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.getSSHKey.returns(p);
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal(errInstance.code);
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.getSSHKey.returns(p);
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('POST /settings/user/keys --- success', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name));
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
it('POST /settings/user/keys --- return parameter error', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name));
request(app)
.post("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("You need to have body or body.name");
done();
});
});
it('POST /settings/user/keys --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p);
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('POST /settings/user/keys --- return Unexpected error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p);
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- success', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(Promise.resolve(true));
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.be.deepEqual({});
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p);
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
var p = Promise.reject(errInstance);
p.catch(()=>{});
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p);
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
});
*/

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var should = require("should"); var should = require("should");
var when = require("when");
var sinon = require("sinon"); var sinon = require("sinon");
var path = require("path"); var path = require("path");
@ -45,6 +44,7 @@ describe("runtime", function() {
log: sinon.stub(), log: sinon.stub(),
warn: sinon.stub(), warn: sinon.stub(),
info: sinon.stub(), info: sinon.stub(),
trace: sinon.stub(),
metric: sinon.stub().returns(!!metrics), metric: sinon.stub().returns(!!metrics),
_: function() { return "abc"} _: function() { return "abc"}
}, },
@ -90,11 +90,11 @@ describe("runtime", function() {
var redNodesLoadFlows; var redNodesLoadFlows;
var redNodesStartFlows; var redNodesStartFlows;
beforeEach(function() { beforeEach(function() {
storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();}); storageInit = sinon.stub(storage,"init",function(settings) {return Promise.resolve();});
redNodesInit = sinon.stub(redNodes,"init", function() {}); redNodesInit = sinon.stub(redNodes,"init", function() {});
redNodesLoad = sinon.stub(redNodes,"load", function() {return when.resolve()}); redNodesLoad = sinon.stub(redNodes,"load", function() {return Promise.resolve()});
redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){}); redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){});
redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return when.resolve()}); redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return Promise.resolve()});
redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {}); redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {});
}); });
afterEach(function() { afterEach(function() {
@ -114,7 +114,7 @@ describe("runtime", function() {
].filter(cb); ].filter(cb);
}); });
var util = mockUtil(); var util = mockUtil();
runtime.init({testSettings: true, httpAdminRoot:"/", load:function() { return when.resolve();}},util); runtime.init({testSettings: true, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util);
// sinon.stub(console,"log"); // sinon.stub(console,"log");
runtime.start().then(function() { runtime.start().then(function() {
// console.log.restore(); // console.log.restore();
@ -143,9 +143,9 @@ describe("runtime", function() {
{ module:"node-red",enabled:true,loaded:false,types:["typeC","typeD"]} // missing { module:"node-red",enabled:true,loaded:false,types:["typeC","typeD"]} // missing
].filter(cb); ].filter(cb);
}); });
var serverInstallModule = sinon.stub(redNodes,"installModule",function(name) { return when.resolve({nodes:[]});}); var serverInstallModule = sinon.stub(redNodes,"installModule",function(name) { return Promise.resolve({nodes:[]});});
var util = mockUtil(); var util = mockUtil();
runtime.init({testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return when.resolve();}},util); runtime.init({testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util);
sinon.stub(console,"log"); sinon.stub(console,"log");
runtime.start().then(function() { runtime.start().then(function() {
console.log.restore(); console.log.restore();
@ -172,7 +172,7 @@ describe("runtime", function() {
].filter(cb); ].filter(cb);
}); });
var util = mockUtil(); var util = mockUtil();
runtime.init({testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return when.resolve();}},util); runtime.init({testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util);
sinon.stub(console,"log"); sinon.stub(console,"log");
runtime.start().then(function() { runtime.start().then(function() {
console.log.restore(); console.log.restore();
@ -190,7 +190,7 @@ describe("runtime", function() {
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} );
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []}); redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
var util = mockUtil(true); var util = mockUtil(true);
runtime.init({testSettings: true, runtimeMetricInterval:200, httpAdminRoot:"/", load:function() { return when.resolve();}},util); runtime.init({testSettings: true, runtimeMetricInterval:200, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util);
sinon.stub(console,"log"); sinon.stub(console,"log");
runtime.start().then(function() { runtime.start().then(function() {
console.log.restore(); console.log.restore();

View File

@ -14,6 +14,167 @@
* limitations under the License. * limitations under the License.
**/ **/
var should = require("should");
var sinon = require("sinon");
var fs = require("fs");
var library = require("../../../../red/runtime/library/index")
var mockLog = {
log: sinon.stub(),
debug: sinon.stub(),
trace: sinon.stub(),
warn: sinon.stub(),
info: sinon.stub(),
metric: sinon.stub(),
audit: sinon.stub(),
_: function() { return "abc"}
}
describe("runtime/library", function() { describe("runtime/library", function() {
it.skip('more tests needed', function(){}) describe("register", function() {
// it("throws error for duplicate type", function() {
// library.init({});
// library.register("unknown","/abc");
// should(()=>{library.register("unknown","/abc")} ).throw();
// })
})
describe("getEntry", function() {
before(function() {
library.init({
log: mockLog,
storage: {
getLibraryEntry: function(type,path) {
return Promise.resolve({type,path});
},
getFlow: function(path) {
return Promise.resolve({path});
}
},
nodes: {
getNodeExampleFlowPath: function(module,entryPath) {
if (module === "unknown") {
return null;
}
return "/tmp/"+module+"/"+entryPath;
}
}
});
sinon.stub(fs,"readFile", function(path,opts,callback) {
if (path === "/tmp/test-module/abc") {
callback(null,"Example flow result");
} else if (path === "/tmp/@scope/test-module/abc") {
callback(null,"Example scope flow result");
} else if (path === "/tmp/test-module/throw") {
throw new Error("Instant error")
} else {
callback(new Error("Unexpected path:"+path))
}
})
});
after(function() {
fs.readFile.restore();
})
it('throws error for unregistered type', function() {
should(()=>{library.getEntry("unknown","/abc")} ).throw();
});
it('returns a registered non-flow entry', function(done) {
library.register("test-module","test-type");
library.getEntry("test-type","/abc").then(function(result) {
result.should.have.property("type","test-type")
result.should.have.property("path","/abc")
done();
}).catch(done);
});
it ('returns a flow entry', function(done) {
library.getEntry("flows","/abc").then(function(result) {
result.should.have.property("path","/abc")
done();
}).catch(done);
});
it ('returns a flow example entry', function(done) {
library.getEntry("flows","_examples_/test-module/abc").then(function(result) {
result.should.eql("Example flow result");
done();
}).catch(done);
});
it ('returns a flow example entry from scoped module', function(done) {
library.getEntry("flows","_examples_/@scope/test-module/abc").then(function(result) {
result.should.eql("Example scope flow result");
done();
}).catch(done);
});
it ('returns an error for unknown flow example entry', function(done) {
library.getEntry("flows","_examples_/unknown/abc").then(function(result) {
done(new Error("No error thrown"))
}).catch(function(err) {
err.should.have.property("code","not_found");
done();
});
});
it ('returns an error for file load error - async', function(done) {
library.getEntry("flows","_examples_/test-module/unknown").then(function(result) {
done(new Error("No error thrown"))
}).catch(function(err) {
done();
});
});
it ('returns an error for file load error - sync', function(done) {
library.getEntry("flows","_examples_/test-module/throw").then(function(result) {
done(new Error("No error thrown"))
}).catch(function(err) {
done();
});
});
});
describe("saveEntry", function() {
before(function() {
library.init({
log: mockLog,
storage: {
saveLibraryEntry: function(type, path, meta, body) {
return Promise.resolve({type,path,meta,body})
},
saveFlow: function(path,body) {
return Promise.resolve({path,body});
}
},
nodes: {
getNodeExampleFlowPath: function(module,entryPath) {
if (module === "unknown") {
return null;
}
return "/tmp/"+module+"/"+entryPath;
}
}
});
});
it('throws error for unregistered type', function() {
should(()=>{library.saveEntry("unknown","/abc",{id:"meta"},{id:"body"})} ).throw();
});
it('saves a flow entry', function(done) {
library.saveEntry('flows','/abc',{id:"meta"},{id:"body"}).then(function(result) {
result.should.have.property("path","/abc");
result.should.have.property("body",{id:"body"});
done();
}).catch(done);
})
it('saves a non-flow entry', function(done) {
library.register("test-module","test-type");
library.saveEntry('test-type','/abc',{id:"meta"},{id:"body"}).then(function(result) {
result.should.have.property("type","test-type");
result.should.have.property("path","/abc");
result.should.have.property("meta",{id:"meta"});
result.should.have.property("body",{id:"body"});
done();
}).catch(done);
})
});
}); });

View File

@ -110,7 +110,7 @@ describe('Node', function() {
p.then(function() { p.then(function() {
callbacksClosed.should.eql(3); callbacksClosed.should.eql(3);
testdone(); testdone();
}).otherwise(function(e) { }).catch(function(e) {
testdone(e); testdone(e);
}); });
}); });

View File

@ -427,7 +427,7 @@ describe('red/runtime/nodes/credentials', function() {
// credentials.dirty().should.be.true(); // credentials.dirty().should.be.true();
// should.not.exist(credentials.get("node")); // should.not.exist(credentials.get("node"));
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
err.should.have.property('code','credentials_load_failed'); err.should.have.property('code','credentials_load_failed');
done(); done();
}); });
@ -443,7 +443,7 @@ describe('red/runtime/nodes/credentials', function() {
// credentials.dirty().should.be.true(); // credentials.dirty().should.be.true();
// should.not.exist(credentials.get("node")); // should.not.exist(credentials.get("node"));
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
err.should.have.property('code','credentials_load_failed'); err.should.have.property('code','credentials_load_failed');
done(); done();
}); });

View File

@ -524,7 +524,7 @@ describe('flows/index', function() {
] ]
}).then(function() { }).then(function() {
done(new Error('failed to reject duplicate node id')); done(new Error('failed to reject duplicate node id'));
}).otherwise(function(err) { }).catch(function(err) {
done(); done();
}) })
}); });
@ -559,7 +559,7 @@ describe('flows/index', function() {
createdFlows.should.have.lengthOf(3); createdFlows.should.have.lengthOf(3);
createdFlows[2].should.eql(id); createdFlows[2].should.eql(id);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}) })
}); });

View File

@ -19,7 +19,6 @@ var fs = require('fs-extra');
var path = require('path'); var path = require('path');
var when = require("when"); var when = require("when");
var sinon = require('sinon'); var sinon = require('sinon');
console.log(__dirname);
var index = require("../../../../red/runtime/nodes/index"); var index = require("../../../../red/runtime/nodes/index");
var flows = require("../../../../red/runtime/nodes/flows"); var flows = require("../../../../red/runtime/nodes/flows");
var registry = require("../../../../red/runtime/nodes/registry"); var registry = require("../../../../red/runtime/nodes/registry");
@ -81,7 +80,7 @@ describe("red/nodes/index", function() {
testnode.credentials.should.have.property('c',"2"); testnode.credentials.should.have.property('c',"2");
testnode.credentials.should.have.property('d',"bar"); testnode.credentials.should.have.property('d',"bar");
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -93,7 +92,7 @@ describe("red/nodes/index", function() {
// console.log(index.getFlows()); // console.log(index.getFlows());
should.deepEqual(testFlows, index.getFlows().flows); should.deepEqual(testFlows, index.getFlows().flows);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
@ -258,7 +257,7 @@ describe("red/nodes/index", function() {
info.should.eql(randomNodeInfo); info.should.eql(randomNodeInfo);
done(); done();
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -273,7 +272,7 @@ describe("red/nodes/index", function() {
}).should.throw(); }).should.throw();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -288,7 +287,7 @@ describe("red/nodes/index", function() {
}).should.throw(); }).should.throw();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -341,7 +340,7 @@ describe("red/nodes/index", function() {
}).should.throw(); }).should.throw();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -356,7 +355,7 @@ describe("red/nodes/index", function() {
}).should.throw(); }).should.throw();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });

View File

@ -60,7 +60,7 @@ describe('red/nodes/registry/index', function() {
registry.addModule("foo").then(function(info) { registry.addModule("foo").then(function(info) {
info.should.eql("info"); info.should.eql("info");
done(); done();
}).otherwise(function(err) { done(err); }); }).catch(function(err) { done(err); });
}); });
it('rejects if loader rejects', function(done) { it('rejects if loader rejects', function(done) {
stubs.push(sinon.stub(loader,"addModule",function(module) { stubs.push(sinon.stub(loader,"addModule",function(module) {
@ -71,7 +71,7 @@ describe('red/nodes/registry/index', function() {
})); }));
registry.addModule("foo").then(function(info) { registry.addModule("foo").then(function(info) {
done(new Error("unexpected resolve")); done(new Error("unexpected resolve"));
}).otherwise(function(err) { }).catch(function(err) {
err.should.eql("error"); err.should.eql("error");
done(); done();
}) })
@ -90,7 +90,7 @@ describe('red/nodes/registry/index', function() {
typeRegistry.enableNodeSet.called.should.be.true(); typeRegistry.enableNodeSet.called.should.be.true();
ns.should.have.a.property('id','node-set'); ns.should.have.a.property('id','node-set');
done(); done();
}).otherwise(function(err) { done(err); }); }).catch(function(err) { done(err); });
}); });
it('rejects if node unknown',function() { it('rejects if node unknown',function() {
@ -121,7 +121,7 @@ describe('red/nodes/registry/index', function() {
ns.should.have.a.property('id','node-set'); ns.should.have.a.property('id','node-set');
ns.should.have.a.property('loaded',true); ns.should.have.a.property('loaded',true);
done(); done();
}).otherwise(function(err) { done(err); }); }).catch(function(err) { done(err); });
}); });
}); });

View File

@ -83,7 +83,7 @@ describe('nodes/registry/installer', function() {
return ee; return ee;
}); });
installer.installModule("this_wont_exist").otherwise(function(err) { installer.installModule("this_wont_exist").catch(function(err) {
err.should.have.property("code",404); err.should.have.property("code",404);
done(); done();
}); });
@ -105,7 +105,7 @@ describe('nodes/registry/installer', function() {
} }
}); });
installer.installModule("this_wont_exist","0.1.2").otherwise(function(err) { installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
err.code.should.be.eql(404); err.code.should.be.eql(404);
done(); done();
}); });
@ -116,7 +116,7 @@ describe('nodes/registry/installer', function() {
version: "0.1.1" version: "0.1.1"
} }
}); });
installer.installModule("this_wont_exist","0.1.1").otherwise(function(err) { installer.installModule("this_wont_exist","0.1.1").catch(function(err) {
err.code.should.be.eql('module_already_loaded'); err.code.should.be.eql('module_already_loaded');
done(); done();
}); });
@ -135,7 +135,7 @@ describe('nodes/registry/installer', function() {
installer.installModule("this_wont_exist").then(function() { installer.installModule("this_wont_exist").then(function() {
done(new Error("Unexpected success")); done(new Error("Unexpected success"));
}).otherwise(function(err) { }).catch(function(err) {
done(); done();
}); });
}); });
@ -160,7 +160,7 @@ describe('nodes/registry/installer', function() {
// commsMessages[0].topic.should.equal("node/added"); // commsMessages[0].topic.should.equal("node/added");
// commsMessages[0].msg.should.eql(nodeInfo.nodes); // commsMessages[0].msg.should.eql(nodeInfo.nodes);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -169,7 +169,7 @@ describe('nodes/registry/installer', function() {
var resourcesDir = path.resolve(path.join(__dirname,"..","resources","local","TestNodeModule","node_modules","NonExistant")); var resourcesDir = path.resolve(path.join(__dirname,"..","resources","local","TestNodeModule","node_modules","NonExistant"));
installer.installModule(resourcesDir).then(function() { installer.installModule(resourcesDir).then(function() {
done(new Error("Unexpected success")); done(new Error("Unexpected success"));
}).otherwise(function(err) { }).catch(function(err) {
if (err.hasOwnProperty("code")) { if (err.hasOwnProperty("code")) {
err.code.should.eql(404); err.code.should.eql(404);
done(); done();
@ -199,7 +199,7 @@ describe('nodes/registry/installer', function() {
installer.installModule(resourcesDir).then(function(info) { installer.installModule(resourcesDir).then(function(info) {
info.should.eql(nodeInfo); info.should.eql(nodeInfo);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -228,7 +228,7 @@ describe('nodes/registry/installer', function() {
installer.uninstallModule("this_wont_exist").then(function() { installer.uninstallModule("this_wont_exist").then(function() {
done(new Error("Unexpected success")); done(new Error("Unexpected success"));
}).otherwise(function(err) { }).catch(function(err) {
done(); done();
}); });
}); });
@ -252,7 +252,7 @@ describe('nodes/registry/installer', function() {
// commsMessages[0].topic.should.equal("node/removed"); // commsMessages[0].topic.should.equal("node/removed");
// commsMessages[0].msg.should.eql(nodeInfo); // commsMessages[0].msg.should.eql(nodeInfo);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });

View File

@ -69,7 +69,7 @@ describe("red/nodes/registry/loader",function() {
loader.load("foo",true).then(function() { loader.load("foo",true).then(function() {
registry.saveNodeList.called.should.be.true(); registry.saveNodeList.called.should.be.true();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}) })
}); });
@ -118,7 +118,7 @@ describe("red/nodes/registry/loader",function() {
nodes.registerType.lastCall.args[1].should.eql('test-node-1'); nodes.registerType.lastCall.args[1].should.eql('test-node-1');
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -169,7 +169,7 @@ describe("red/nodes/registry/loader",function() {
nodes.registerType.secondCall.args[1].should.eql('test-node-multiple-1b'); nodes.registerType.secondCall.args[1].should.eql('test-node-multiple-1b');
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -219,7 +219,7 @@ describe("red/nodes/registry/loader",function() {
nodes.registerType.lastCall.args[1].should.eql('test-node-2'); nodes.registerType.lastCall.args[1].should.eql('test-node-2');
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -267,7 +267,7 @@ describe("red/nodes/registry/loader",function() {
nodes.registerType.calledOnce.should.be.false(); nodes.registerType.calledOnce.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -313,7 +313,7 @@ describe("red/nodes/registry/loader",function() {
nodes.registerType.calledOnce.should.be.false(); nodes.registerType.calledOnce.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -360,7 +360,7 @@ describe("red/nodes/registry/loader",function() {
nodes.registerType.calledOnce.should.be.false(); nodes.registerType.calledOnce.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -379,7 +379,7 @@ describe("red/nodes/registry/loader",function() {
stubs.push(sinon.stub(registry,"getModuleInfo",function(){return{}})); stubs.push(sinon.stub(registry,"getModuleInfo",function(){return{}}));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.addModule("test-module").otherwise(function(err) { loader.addModule("test-module").catch(function(err) {
err.code.should.eql("module_already_loaded"); err.code.should.eql("module_already_loaded");
done(); done();
}); });
@ -390,7 +390,7 @@ describe("red/nodes/registry/loader",function() {
throw new Error("failure"); throw new Error("failure");
})); }));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.addModule("test-module").otherwise(function(err) { loader.addModule("test-module").catch(function(err) {
err.message.should.eql("failure"); err.message.should.eql("failure");
done(); done();
}); });
@ -441,7 +441,7 @@ describe("red/nodes/registry/loader",function() {
nodes.registerType.calledOnce.should.be.true(); nodes.registerType.calledOnce.should.be.true();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -477,7 +477,7 @@ describe("red/nodes/registry/loader",function() {
registry.addNodeSet.called.should.be.false(); registry.addNodeSet.called.should.be.false();
nodes.registerType.called.should.be.false(); nodes.registerType.called.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -498,7 +498,7 @@ describe("red/nodes/registry/loader",function() {
node.enabled.should.be.false(); node.enabled.should.be.false();
nodes.registerType.called.should.be.false(); nodes.registerType.called.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -517,7 +517,7 @@ describe("red/nodes/registry/loader",function() {
node.err.toString().should.eql("Error: fail to require (line:1)"); node.err.toString().should.eql("Error: fail to require (line:1)");
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });

View File

@ -292,7 +292,7 @@ describe("red/nodes/registry/registry",function() {
it('rejects when settings unavailable',function(done) { it('rejects when settings unavailable',function(done) {
typeRegistry.init(stubSettings({},false,{})); typeRegistry.init(stubSettings({},false,{}));
typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1"); typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1");
typeRegistry.saveNodeList().otherwise(function(err) { typeRegistry.saveNodeList().catch(function(err) {
done(); done();
}); });
}); });
@ -312,7 +312,7 @@ describe("red/nodes/registry/registry",function() {
nn.should.not.have.property('id'); nn.should.not.have.property('id');
} }
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var should = require("should"); var should = require("should");
var when = require("when");
var settings = require("../../../red/runtime/settings"); var settings = require("../../../red/runtime/settings");
@ -86,12 +85,12 @@ describe("red/settings", function() {
var saveCount = 0; var saveCount = 0;
var storage = { var storage = {
getSettings: function() { getSettings: function() {
return when.resolve({globalA:789}); return Promise.resolve({globalA:789});
}, },
saveSettings: function(settings) { saveSettings: function(settings) {
saveCount++; saveCount++;
savedSettings = settings; savedSettings = settings;
return when.resolve(); return Promise.resolve();
} }
} }
settings.init(userSettings); settings.init(userSettings);
@ -115,7 +114,7 @@ describe("red/settings", function() {
done(); done();
}); });
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });

View File

@ -47,7 +47,7 @@ describe('storage/localfilesystem', function() {
fs.existsSync(path.join(userDir,"lib")).should.be.true(); fs.existsSync(path.join(userDir,"lib")).should.be.true();
fs.existsSync(path.join(userDir,"lib",'flows')).should.be.true(); fs.existsSync(path.join(userDir,"lib",'flows')).should.be.true();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -70,7 +70,7 @@ describe('storage/localfilesystem', function() {
} finally { } finally {
process.env.NODE_RED_HOME = oldNRH; process.env.NODE_RED_HOME = oldNRH;
} }
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -96,7 +96,7 @@ describe('storage/localfilesystem', function() {
process.env.NODE_RED_HOME = oldNRH; process.env.NODE_RED_HOME = oldNRH;
process.env.NODE_HOMEPATH = oldHOMEPATH; process.env.NODE_HOMEPATH = oldHOMEPATH;
} }
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -124,7 +124,7 @@ describe('storage/localfilesystem', function() {
process.env.HOME = oldHOME; process.env.HOME = oldHOME;
process.env.HOMEPATH = oldHOMEPATH; process.env.HOMEPATH = oldHOMEPATH;
} }
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -155,7 +155,7 @@ describe('storage/localfilesystem', function() {
process.env.HOMEPATH = oldHOMEPATH; process.env.HOMEPATH = oldHOMEPATH;
process.env.USERPROFILE = oldUSERPROFILE; process.env.USERPROFILE = oldUSERPROFILE;
} }
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -168,10 +168,10 @@ describe('storage/localfilesystem', function() {
localfilesystem.getFlows().then(function(flows) { localfilesystem.getFlows().then(function(flows) {
flows.should.eql([]); flows.should.eql([]);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -186,10 +186,10 @@ describe('storage/localfilesystem', function() {
localfilesystem.getFlows().then(function(flows) { localfilesystem.getFlows().then(function(flows) {
flows.should.eql([]); flows.should.eql([]);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -208,11 +208,11 @@ describe('storage/localfilesystem', function() {
localfilesystem.getFlows().then(function(flows) { localfilesystem.getFlows().then(function(flows) {
flows.should.eql(testFlow); flows.should.eql(testFlow);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
},50); },50);
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -230,13 +230,13 @@ describe('storage/localfilesystem', function() {
localfilesystem.getFlows().then(function(flows) { localfilesystem.getFlows().then(function(flows) {
flows.should.eql(testFlow); flows.should.eql(testFlow);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -257,13 +257,13 @@ describe('storage/localfilesystem', function() {
localfilesystem.getFlows().then(function(flows) { localfilesystem.getFlows().then(function(flows) {
flows.should.eql(testFlow); flows.should.eql(testFlow);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -278,13 +278,13 @@ describe('storage/localfilesystem', function() {
localfilesystem.getFlows().then(function(flows) { localfilesystem.getFlows().then(function(flows) {
flows.should.eql(testFlow); flows.should.eql(testFlow);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -298,11 +298,11 @@ describe('storage/localfilesystem', function() {
fs.fsync.callCount.should.be.greaterThan(0); fs.fsync.callCount.should.be.greaterThan(0);
fs.fsync.restore(); fs.fsync.restore();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
fs.fsync.restore(); fs.fsync.restore();
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -321,10 +321,10 @@ describe('storage/localfilesystem', function() {
fs.fsync.callCount.should.be.greaterThan(0); fs.fsync.callCount.should.be.greaterThan(0);
fs.fsync.restore(); fs.fsync.restore();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -358,14 +358,14 @@ describe('storage/localfilesystem', function() {
content2.should.not.equal(backupContent); content2.should.not.equal(backupContent);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
@ -382,10 +382,10 @@ describe('storage/localfilesystem', function() {
localfilesystem.getCredentials().then(function(creds) { localfilesystem.getCredentials().then(function(creds) {
creds.should.eql({}); creds.should.eql({});
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -406,13 +406,13 @@ describe('storage/localfilesystem', function() {
localfilesystem.getCredentials().then(function(creds) { localfilesystem.getCredentials().then(function(creds) {
creds.should.eql(credentials); creds.should.eql(credentials);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -437,10 +437,10 @@ describe('storage/localfilesystem', function() {
fs.existsSync(credFile).should.be.true(); fs.existsSync(credFile).should.be.true();
fs.existsSync(credFileBackup).should.be.true(); fs.existsSync(credFileBackup).should.be.true();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -463,13 +463,13 @@ describe('storage/localfilesystem', function() {
localfilesystem.getCredentials().then(function(creds) { localfilesystem.getCredentials().then(function(creds) {
creds.should.eql(credentials); creds.should.eql(credentials);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });

View File

@ -38,7 +38,7 @@ describe('storage/localfilesystem/sessions', function() {
localfilesystemSessions.getSessions().then(function(sessions) { localfilesystemSessions.getSessions().then(function(sessions) {
sessions.should.eql({}); sessions.should.eql({});
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -51,7 +51,7 @@ describe('storage/localfilesystem/sessions', function() {
localfilesystemSessions.getSessions().then(function(sessions) { localfilesystemSessions.getSessions().then(function(sessions) {
sessions.should.eql({}); sessions.should.eql({});
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -69,10 +69,10 @@ describe('storage/localfilesystem/sessions', function() {
localfilesystemSessions.getSessions().then(function(_sessions) { localfilesystemSessions.getSessions().then(function(_sessions) {
_sessions.should.eql(sessions); _sessions.should.eql(sessions);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });

View File

@ -39,7 +39,7 @@ describe('storage/localfilesystem/settings', function() {
localfilesystemSettings.getSettings().then(function(settings) { localfilesystemSettings.getSettings().then(function(settings) {
settings.should.eql({}); settings.should.eql({});
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -52,7 +52,7 @@ describe('storage/localfilesystem/settings', function() {
localfilesystemSettings.getSettings().then(function(settings) { localfilesystemSettings.getSettings().then(function(settings) {
settings.should.eql({}); settings.should.eql({});
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -70,10 +70,10 @@ describe('storage/localfilesystem/settings', function() {
localfilesystemSettings.getSettings().then(function(_settings) { localfilesystemSettings.getSettings().then(function(_settings) {
_settings.should.eql(settings); _settings.should.eql(settings);
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });