From 5d064aa1d779ea7eef683dedada40190dafb50af Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 24 Apr 2018 15:01:49 +0100 Subject: [PATCH] Fixup all the tests --- red/api/admin/flows.js | 5 +- red/api/admin/nodes.js | 8 +- red/api/editor/comms.js | 5 +- red/api/editor/index.js | 3 +- red/api/editor/sshkeys.js | 18 +- red/api/index.js | 14 - red/api/util.js | 13 +- red/red.js | 17 +- red/runtime-api/comms.js | 4 +- red/runtime-api/flows.js | 1 + red/runtime/index.js | 3 - red/runtime/library/index.js | 32 +- red/runtime/nodes/registry/loader.js | 16 +- test/nodes/helper.js | 2 +- test/red/api/admin/flow_spec.js | 186 ++-- test/red/api/admin/flows_spec.js | 121 +-- test/red/api/admin/nodes_spec.js | 696 ++++----------- test/red/api/auth/index_spec.js | 104 ++- test/red/api/auth/strategies_spec.js | 3 - test/red/api/editor/comms_spec.js | 149 ++-- test/red/api/editor/credentials_spec.js | 83 +- test/red/api/editor/index_spec.js | 20 +- test/red/api/editor/library_spec.js | 549 ++++++------ test/red/api/editor/locales_spec.js | 43 +- test/red/api/editor/settings_spec.js | 272 ++---- test/red/api/editor/sshkeys_spec.js | 185 ++-- test/red/api/editor/theme_spec.js | 7 +- test/red/api/editor/ui_spec.js | 11 +- test/red/api/index_spec.js | 15 +- test/red/api/util_spec.js | 33 +- test/red/runtime-api/comms_spec.js | 225 ++++- test/red/runtime-api/flows_spec.js | 272 ++++++ test/red/runtime-api/index_spec.js | 37 +- test/red/runtime-api/library_spec.js | 339 +++++++ test/red/runtime-api/nodes_spec.js | 826 ++++++++++++++++++ test/red/runtime-api/settings_spec.js | 566 ++++++++++++ test/red/runtime/index_spec.js | 18 +- test/red/runtime/library/index_spec.js | 163 +++- test/red/runtime/nodes/Node_spec.js | 2 +- test/red/runtime/nodes/credentials_spec.js | 4 +- test/red/runtime/nodes/flows/index_spec.js | 4 +- test/red/runtime/nodes/index_spec.js | 15 +- test/red/runtime/nodes/registry/index_spec.js | 8 +- .../runtime/nodes/registry/installer_spec.js | 18 +- .../red/runtime/nodes/registry/loader_spec.js | 26 +- .../runtime/nodes/registry/registry_spec.js | 4 +- test/red/runtime/settings_spec.js | 7 +- .../storage/localfilesystem/index_spec.js | 74 +- .../storage/localfilesystem/sessions_spec.js | 8 +- .../storage/localfilesystem/settings_spec.js | 8 +- 50 files changed, 3480 insertions(+), 1762 deletions(-) diff --git a/red/api/admin/flows.js b/red/api/admin/flows.js index 35b234dfa..ccad8718f 100644 --- a/red/api/admin/flows.js +++ b/red/api/admin/flows.js @@ -15,6 +15,7 @@ **/ var runtimeAPI; +var apiUtils = require("../util"); module.exports = { init: function(_runtimeAPI) { @@ -23,7 +24,7 @@ module.exports = { get: function(req,res) { var version = req.get("Node-RED-API-Version")||"v1"; 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 = { user: req.user @@ -41,7 +42,7 @@ module.exports = { post: function(req,res) { var version = req.get("Node-RED-API-Version")||"v1"; 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 = { user: req.user, diff --git a/red/api/admin/nodes.js b/red/api/admin/nodes.js index 6dbe1cc05..31f231f78 100644 --- a/red/api/admin/nodes.js +++ b/red/api/admin/nodes.js @@ -56,7 +56,7 @@ module.exports = { user: req.user, module: req.params[0] } - runtimeAPI.nodes.removeModule(opts).then(function(info) { + runtimeAPI.nodes.removeModule(opts).then(function() { res.status(204).end(); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); @@ -77,7 +77,7 @@ module.exports = { } else { opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); runtimeAPI.nodes.getNodeConfig(opts).then(function(result) { - return res.json(result); + return res.send(result); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) @@ -100,7 +100,7 @@ module.exports = { var body = req.body; if (!body.hasOwnProperty("enabled")) { // 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; } var opts = { @@ -119,7 +119,7 @@ module.exports = { var body = req.body; if (!body.hasOwnProperty("enabled")) { // 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; } var opts = { diff --git a/red/api/editor/comms.js b/red/api/editor/comms.js index b7d2fea89..10741bd08 100644 --- a/red/api/editor/comms.js +++ b/red/api/editor/comms.js @@ -28,6 +28,8 @@ var runtimeAPI; var wsServer; var activeConnections = []; +var anonymousUser; + var retained = {}; var heartbeatTimer; @@ -173,7 +175,8 @@ CommsConnection.prototype.subscribe = function(topic) { function start() { if (!settings.disableEditor) { - Users.default().then(function(anonymousUser) { + Users.default().then(function(_anonymousUser) { + anonymousUser = _anonymousUser; var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000; var path = settings.httpAdminRoot || "/"; path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms"; diff --git a/red/api/editor/index.js b/red/api/editor/index.js index 58e698d03..fb617c154 100644 --- a/red/api/editor/index.js +++ b/red/api/editor/index.js @@ -89,7 +89,6 @@ module.exports = { // Library var library = require("./library"); library.init(runtimeAPI); - editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler); editorApp.get(/library\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry); editorApp.post(/library\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry); @@ -107,7 +106,7 @@ module.exports = { // User Settings editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler); // SSH keys - editorApp.use("/settings/user/keys",info.sshkeys()); + editorApp.use("/settings/user/keys",needsPermission("settings.write"),info.sshkeys()); return editorApp; } diff --git a/red/api/editor/sshkeys.js b/red/api/editor/sshkeys.js index 62ccdc574..3e7b0de87 100644 --- a/red/api/editor/sshkeys.js +++ b/red/api/editor/sshkeys.js @@ -14,9 +14,9 @@ * limitations under the License. **/ +var apiUtils = require("../util"); var express = require("express"); var runtimeAPI; -var needsPermission = require("../auth").needsPermission; function getUsername(userObj) { var username = '__default'; @@ -34,7 +34,7 @@ module.exports = { var app = express(); // List all SSH keys - app.get("/", needsPermission("settings.read"), function(req,res) { + app.get("/", function(req,res) { var opts = { user: req.user } @@ -48,7 +48,7 @@ module.exports = { }); // Get SSH key detail - app.get("/:id", needsPermission("settings.read"), function(req,res) { + app.get("/:id", function(req,res) { var opts = { user: req.user, id: req.params.id @@ -63,11 +63,17 @@ module.exports = { }); // Generate a SSH key - app.post("/", needsPermission("settings.write"), function(req,res) { + app.post("/", function(req,res) { var opts = { user: req.user, 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) { res.json({ name: name @@ -78,12 +84,12 @@ module.exports = { }); // Delete a SSH key - app.delete("/:id", needsPermission("settings.write"), function(req,res) { + app.delete("/:id", function(req,res) { var opts = { user: req.user, id: req.params.id } - runtimeAPI.settings.generateUserKey(opts).then(function(name) { + runtimeAPI.settings.removeUserKey(opts).then(function(name) { res.status(204).end(); }).catch(function(err) { apiUtils.rejectHandler(req,res,err); diff --git a/red/api/index.js b/red/api/index.js index 298dacdd5..91059ce6b 100644 --- a/red/api/index.js +++ b/red/api/index.js @@ -89,23 +89,9 @@ module.exports = { init: init, start: start, stop: stop, - library: { - register: function(type) { - if (editor) { - editor.registerLibrary(type); - } - } - }, auth: { needsPermission: auth.needsPermission }, - comms: { - publish: function(topic,data,retain) { - if (editor) { - editor.publish(topic,data,retain); - } - } - }, get adminApp() { return adminApp; }, get server() { return server; } }; diff --git a/red/api/util.js b/red/api/util.js index 6a6d97e1d..4f87e3ce2 100644 --- a/red/api/util.js +++ b/red/api/util.js @@ -21,7 +21,6 @@ var i18n = require("../util").i18n; // TODO: separate module module.exports = { errorHandler: function(err,req,res,next) { - console.error(err.stack); if (err.message === "request entity too large") { log.error(err); } else { @@ -40,13 +39,9 @@ module.exports = { return lang; }, rejectHandler: function(req,res,err) { - res.status(err.status||500); - if (err.code || err.message) { - res.json({ - code: err.code||"unexpected_error", - message: err.message - }) - } - res.end(); + res.status(err.status||500).json({ + code: err.code||"unexpected_error", + message: err.message||err.toString() + }); } } diff --git a/red/red.js b/red/red.js index 717d74507..5e9da6402 100644 --- a/red/red.js +++ b/red/red.js @@ -111,9 +111,20 @@ module.exports = { util: runtime.util, version: runtime.version, events: runtime.events, - - comms: api.comms, - library: api.library, + comms: { + publish: function(topic,data,retain) { + runtime.events.emit("comms",{ + topic: topic, + data: data, + retain: retain + }) + } + }, + library: { + register: function(type) { + return runtime.library.register(null,type); + } + }, auth: api.auth, get app() { console.log("Deprecated use of RED.app - use RED.httpAdmin instead"); return runtime.app }, diff --git a/red/runtime-api/comms.js b/red/runtime-api/comms.js index d89123795..3ab361f3d 100644 --- a/red/runtime-api/comms.js +++ b/red/runtime-api/comms.js @@ -49,13 +49,15 @@ function publish(topic,data,retain) { } else { delete retained[topic]; } - connections.forEach(connection => connection.send(topic,data,retain)) + connections.forEach(connection => connection.send(topic,data)) } var api = module.exports = { init: function(_runtime) { runtime = _runtime; + connections = []; + retained = {}; runtime.events.removeListener("node-status",handleStatusEvent); runtime.events.on("node-status",handleStatusEvent); runtime.events.removeListener("runtime-event",handleRuntimeEvent); diff --git a/red/runtime-api/flows.js b/red/runtime-api/flows.js index ba52090a0..30a6e83ef 100644 --- a/red/runtime-api/flows.js +++ b/red/runtime-api/flows.js @@ -135,6 +135,7 @@ var api = module.exports = { } else { runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"}); var err = new Error(); + err.code = "not_found"; err.status = 404; return reject(err); } diff --git a/red/runtime/index.js b/red/runtime/index.js index 05f7fcdba..b9a2b21ab 100644 --- a/red/runtime/index.js +++ b/red/runtime/index.js @@ -42,9 +42,6 @@ var stubbedExpressApp = { delete: function() {} } var adminApi = { - library: { - register: function() {} - }, auth: { needsPermission: function() {} }, diff --git a/red/runtime/library/index.js b/red/runtime/library/index.js index ce95d66ff..e0d8b6d82 100644 --- a/red/runtime/library/index.js +++ b/red/runtime/library/index.js @@ -29,17 +29,20 @@ function init(_runtime) { } function registerType(id,type) { - if (knownTypes.hasOwnProperty(type)) { - throw new Error(`Library type '${type}' already registerd by ${id}'`) - } + // TODO: would like to enforce this, but currently the tests register the same type multiple + // 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; } -function getAllEntries(type) { - if (!knownTypes.hasOwnProperty(type)) { - throw new Error(`Unknown library type '${type}'`); - } -} +// function getAllEntries(type) { +// if (!knownTypes.hasOwnProperty(type)) { +// throw new Error(`Unknown library type '${type}'`); +// } +// } + function getEntry(type,path) { if (type !== 'flows') { if (!knownTypes.hasOwnProperty(type)) { @@ -67,12 +70,11 @@ function getEntry(type,path) { return reject(err); } } - } else { - // IF we get here, we didn't find the file - var error = new Error("not_found"); - error.code = "not_found"; - return reject(error); } + // IF we get here, we didn't find the file + var error = new Error("not_found"); + error.code = "not_found"; + return reject(error); } else { resolve(storage.getFlow(path)); } @@ -92,8 +94,8 @@ function saveEntry(type,path,meta,body) { module.exports = { init: init, - registerType: registerType, - getAllEntries: getAllEntries, + register: registerType, + // getAllEntries: getAllEntries, getEntry: getEntry, saveEntry: saveEntry diff --git a/red/runtime/nodes/registry/loader.js b/red/runtime/nodes/registry/loader.js index c580939c3..f4c3bd51f 100644 --- a/red/runtime/nodes/registry/loader.js +++ b/red/runtime/nodes/registry/loader.js @@ -79,6 +79,11 @@ function createNodeApi(node) { retain: retain }) } + }, + library: { + register: function(type) { + return runtime.library.register(node.id,type); + } } } 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.settings,red.settings,null,["init","load","reset"]); if (runtime.adminApi) { - red.library = { - register: function(type) { - return runtime.library.registerType(node.id,type); - } - }; red.auth = runtime.adminApi.auth; red.httpAdmin = runtime.adminApi.adminApp; red.httpNode = runtime.nodeApp; @@ -100,12 +100,6 @@ function createNodeApi(node) { } else { //TODO: runtime.adminApi is always stubbed if not enabled, so this block // is unused - but may be needed for the unit tests - red.comms = { - publish: function() {} - }; - red.library = { - register: function() {} - }; red.auth = { needsPermission: function() {} }; diff --git a/test/nodes/helper.js b/test/nodes/helper.js index 31290d5f3..4e165103b 100644 --- a/test/nodes/helper.js +++ b/test/nodes/helper.js @@ -35,7 +35,7 @@ var redNodes = require("../../red/runtime/nodes"); var flows = require("../../red/runtime/nodes/flows"); var credentials = require("../../red/runtime/nodes/credentials"); 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 events = require("../../red/runtime/events.js"); diff --git a/test/red/api/admin/flow_spec.js b/test/red/api/admin/flow_spec.js index e39b92da8..1f7f9e5e4 100644 --- a/test/red/api/admin/flow_spec.js +++ b/test/red/api/admin/flow_spec.js @@ -38,18 +38,23 @@ describe("api/admin/flow", function() { describe("get", function() { before(function() { + var opts; flow.init({ - settings:{}, - nodes: { - getFlow: function(id) { - if (id === '123') { - return {id:'123'} + flows: { + getFlow: function(_opts) { + opts = _opts; + if (opts.id === '123') { + return Promise.resolve({id:'123'}); } 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) { @@ -75,19 +80,24 @@ describe("api/admin/flow", function() { }); describe("add", function() { + var opts; before(function() { flow.init({ - settings:{}, - nodes: { - addFlow: function(f) { - if (f.id === "123") { - return when.resolve('123') + flows: { + addFlow: function(_opts) { + opts = _opts; + if (opts.flow.id === "123") { + return Promise.resolve('123') } 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) { @@ -114,8 +124,8 @@ describe("api/admin/flow", function() { if (err) { return done(err); } - res.body.should.has.a.property('error','unexpected_error'); - res.body.should.has.a.property('message','Error: test error'); + res.body.should.has.a.property('code','random_error'); + res.body.should.has.a.property('message','random error'); done(); }); @@ -123,35 +133,29 @@ describe("api/admin/flow", function() { }) describe("update", function() { - var nodes; + + var opts; 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({ + flows: { + updateFlow: function(_opts) { + 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; + } } } - }; - flow.init({ - settings:{}, - nodes: nodes, - log:{ audit: sinon.stub() } }); }) it('updates an existing flow', function(done) { - sinon.spy(nodes,"updateFlow"); request(app) .put('/flow/123') .set('Accept', 'application/json') @@ -162,115 +166,79 @@ describe("api/admin/flow", function() { return done(err); } res.body.should.has.a.property('id','123'); - nodes.updateFlow.calledOnce.should.be.true(); - nodes.updateFlow.lastCall.args[0].should.eql('123'); - nodes.updateFlow.lastCall.args[1].should.eql({id:'123'}); - nodes.updateFlow.restore(); + opts.should.have.property('id','123'); + opts.should.have.property('flow',{id:'123'}) done(); }); }) - it('404s on an unknown flow', function(done) { + it('400 an invalid flow', function(done) { request(app) - .put('/flow/unknown') + .put('/flow/456') .set('Accept', 'application/json') - .send({id:'123'}) - .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'}) + .send({id:'456'}) .expect(400) .end(function(err,res) { if (err) { return done(err); } - res.body.should.has.a.property('error','unexpected_error'); - res.body.should.has.a.property('message','Error: test error'); - done(); - }); - }) + res.body.should.has.a.property('code','random_error'); + res.body.should.has.a.property('message','random error'); - 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(); }); }) }) describe("delete", function() { - var nodes; + + var opts; 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({ + flows: { + deleteFlow: function(_opts) { + 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; + } } } - }; - flow.init({ - settings:{}, - nodes: nodes, - log:{ audit: sinon.stub() } }); }) - it('updates an existing flow', function(done) { - sinon.spy(nodes,"removeFlow"); + it('deletes an existing flow', function(done) { request(app) - .delete('/flow/123') + .del('/flow/123') + .set('Accept', 'application/json') .expect(204) .end(function(err,res) { if (err) { return done(err); } - nodes.removeFlow.calledOnce.should.be.true(); - nodes.removeFlow.lastCall.args[0].should.eql('123'); - nodes.removeFlow.restore(); + opts.should.have.property('id','123'); done(); }); }) - it('404s on an unknown flow', function(done) { + it('400 an invalid flow', function(done) { request(app) - .delete('/flow/unknown') - .expect(404) - .end(done); - }) - - it('400 on remove error', function(done) { - request(app) - .delete('/flow/unexpected') + .del('/flow/456') + .set('Accept', 'application/json') .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'); + res.body.should.has.a.property('code','random_error'); + res.body.should.has.a.property('message','random error'); + done(); }); }) diff --git a/test/red/api/admin/flows_spec.js b/test/red/api/admin/flows_spec.js index 57b8de99c..e55b38933 100644 --- a/test/red/api/admin/flows_spec.js +++ b/test/red/api/admin/flows_spec.js @@ -19,7 +19,6 @@ 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"); @@ -36,10 +35,8 @@ describe("api/admin/flows", function() { 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]}; } + flows:{ + getFlows: function() { return Promise.resolve({rev:"123",flows:[1,2,3]}); } } }); request(app) @@ -60,10 +57,8 @@ describe("api/admin/flows", function() { }); 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]}; } + flows:{ + getFlows: function() { return Promise.resolve({rev:"123",flows:[1,2,3]}); } } }); request(app) @@ -104,10 +99,9 @@ describe("api/admin/flows", function() { }); }); 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({ - log:{warn:function(){},_:function(){},audit:function(){}}, - nodes:{ + flows:{ setFlows: setFlows } }); @@ -120,15 +114,14 @@ describe("api/admin/flows", function() { return done(err); } setFlows.calledOnce.should.be.true(); - setFlows.lastCall.args[1].should.eql('full'); + setFlows.lastCall.args[0].should.have.property('deploymentType','full'); 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({ - log:{warn:function(){},_:function(){},audit:function(){}}, - nodes:{ + flows:{ setFlows: setFlows } }); @@ -142,19 +135,22 @@ describe("api/admin/flows", function() { return done(err); } setFlows.calledOnce.should.be.true(); - setFlows.lastCall.args[1].should.eql('nodes'); + setFlows.lastCall.args[0].should.have.property('deploymentType','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 + flows:{ + setFlows: function() { + var err = new Error("mismatch"); + err.code = "version_mismatch"; + err.status = 409; + var p = Promise.reject(err); + p.catch(()=>{}); + return p; + } } }); request(app) @@ -171,54 +167,6 @@ describe("api/admin/flows", function() { 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') @@ -238,11 +186,10 @@ describe("api/admin/flows", function() { }); }); it('reloads flows', function(done) { - var loadFlows = sinon.spy(function() { return when.resolve(); }); + var setFlows = sinon.spy(function() { return Promise.resolve();}); flows.init({ - log:{warn:function(){},_:function(){},audit:function(){}}, - nodes:{ - loadFlows: loadFlows + flows:{ + setFlows: setFlows } }); request(app) @@ -254,29 +201,9 @@ describe("api/admin/flows", function() { if (err) { return done(err); } - loadFlows.called.should.be.true(); + setFlows.called.should.be.true(); + setFlows.lastCall.args[0].should.not.have.property('flows'); 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(); - }); - }); - }); diff --git a/test/red/api/admin/nodes_spec.js b/test/red/api/admin/nodes_spec.js index 1720b2527..fb970a65c 100644 --- a/test/red/api/admin/nodes_spec.js +++ b/test/red/api/admin/nodes_spec.js @@ -27,31 +27,17 @@ 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.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.getSet); app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.putSet); app.get("/getIcons",nodes.getIcons); - app.delete("/nodes/:id",nodes.delete); + app.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.delete); sinon.stub(apiUtil,"determineLangFromHeaders", function() { return "en-US"; }); @@ -62,10 +48,10 @@ describe("api/admin/nodes", function() { describe('get nodes', function() { it('returns node list', function(done) { - initNodes({ + nodes.init({ nodes:{ 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) { - initNodes({ + nodes.init({ nodes:{ getNodeConfigs: function() { - return ""; + return Promise.resolve(""); } }, i18n: { @@ -108,10 +94,10 @@ describe("api/admin/nodes", function() { }); it('returns node module info', function(done) { - initNodes({ + nodes.init({ nodes:{ - getModuleInfo: function(id) { - return {"node-red":{name:"node-red"}}[id]; + getModuleInfo: function(opts) { + 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) { - initNodes({ + nodes.init({ nodes:{ - getModuleInfo: function(id) { - return {"node-red":{name:"node-red"}}[id]; + getModuleInfo: function(opts) { + 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) { - initNodes({ + nodes.init({ nodes:{ - getNodeInfo: function(id) { - return {"node-red/123":{id:"node-red/123"}}[id]; + getNodeInfo: function(opts) { + 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) { - initNodes({ + nodes.init({ nodes:{ - getNodeConfig: function(id) { - return {"node-red/123":""}[id]; + getNodeConfig: function(opts) { + return Promise.resolve({"node-red/123":""}[opts.id]); } }, i18n: { @@ -190,12 +181,16 @@ describe("api/admin/nodes", function() { done(); }); }); - it('returns 404 for unknown node', function(done) { - initNodes({ + nodes.init({ nodes:{ - getNodeInfo: function(id) { - return {"node-red/123":{id:"node-red/123"}}[id]; + getNodeInfo: function(opts) { + var errInstance = new Error("Not Found"); + errInstance.code = "not_found"; + errInstance.status = 404; + var p = Promise.reject(errInstance); + p.catch(()=>{}); + return p; } } }); @@ -213,142 +208,96 @@ describe("api/admin/nodes", function() { }); describe('install', function() { - - it('returns 400 if settings are unavailable', function(done) { - initNodes({ - settings:{available:function(){return false}} + it('installs the module and returns module info', function(done) { + var opts; + nodes.init({ + nodes:{ + addModule: function(_opts) { + opts = _opts; + return Promise.resolve({ + name:"foo", + nodes:[{id:"123"}] + }); + } + } + }); + request(app) + .post('/nodes') + .send({module: 'foo',version:"1.2.3"}) + .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"); + opts.should.have.property("module","foo"); + opts.should.have.property("version","1.2.3"); + done(); + }); + }); + it('returns error', function(done) { + nodes.init({ + nodes:{ + addModule: 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) .post('/nodes') + .send({module: 'foo',version:"1.2.3"}) .expect(400) .end(function(err,res) { if (err) { throw err; } + res.body.should.have.a.property('code','random_error'); 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}} + it('uninstalls the module', function(done) { + var opts; + 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) .del('/nodes/123') .expect(400) @@ -356,93 +305,18 @@ describe("api/admin/nodes", function() { if (err) { throw err; } + res.body.should.have.a.property('code','random_error'); 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}} + describe('enable/disable node set', function() { + it('returns 400 for invalid request payload', function(done) { + nodes.init({ + nodes:{ + setNodeSetState: function(opts) {return Promise.resolve()} + } }); request(app) .put('/nodes/node-red/foo') @@ -452,77 +326,23 @@ describe("api/admin/nodes", function() { if (err) { throw err; } + res.body.should.have.property("code","invalid_request"); 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}}, + it('sets node state and returns node info', function(done) { + var opts; + nodes.init({ 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) .put('/nodes/node-red/foo') .send({enabled:true}) @@ -533,130 +353,41 @@ describe("api/admin/nodes", function() { } res.body.should.have.property("id","123"); res.body.should.have.property("enabled",true); + opts.should.have.property("enabled",true); + opts.should.have.property("id","node-red/foo"); done(); }); }); - - it('disables enabled node', function(done) { - initNodes({ - settings:{available:function(){return true}}, + }); + describe('enable/disable module' ,function() { + it('returns 400 for invalid request payload', function(done) { + nodes.init({ nodes:{ - getNodeInfo: function() { return {id:"123",enabled: true} }, - disableNode: function() { return when.resolve({id:"123",enabled: false,types:['a']}); } + setModuleState: function(opts) {return Promise.resolve()} } }); request(app) - .put('/nodes/node-red/foo') - .send({enabled:false}) - .expect(200) + .put('/nodes/node-red') + .send({}) + .expect(400) .end(function(err,res) { if (err) { throw err; } - res.body.should.have.property("id","123"); - res.body.should.have.property("enabled",false); - + res.body.should.have.property("code","invalid_request"); + res.body.should.have.property("message","Invalid request"); 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}}, + it('sets module state and returns module info', function(done) { + var opts; + nodes.init({ nodes:{ - getModuleInfo: function() { return {name:"node-red", nodes:[n1, n2]} }, - enableNode: enableNode + setModuleState: function(_opts) { + opts = _opts; + return Promise.resolve({name:"node-red"}); + } } }); @@ -669,154 +400,20 @@ describe("api/admin/nodes", function() { 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); + opts.should.have.property("enabled",true); + opts.should.have.property("module","node-red"); 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.init({ nodes:{ - getNodeIcons: function() { - return {"module":["1.png","2.png","3.png"]}; + getIconList: function() { + return Promise.resolve({module:[1,2,3]}); } } }); @@ -827,7 +424,6 @@ describe("api/admin/nodes", function() { 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); diff --git a/test/red/api/auth/index_spec.js b/test/red/api/auth/index_spec.js index 9849b602c..ef01d2391 100644 --- a/test/red/api/auth/index_spec.js +++ b/test/red/api/auth/index_spec.js @@ -23,6 +23,7 @@ var passport = require("passport"); var auth = require("../../../../red/api/auth"); var Users = require("../../../../red/api/auth/users"); var Tokens = require("../../../../red/api/auth/tokens"); +var Permissions = require("../../../../red/api/auth/permissions"); describe("api/auth/index",function() { @@ -30,7 +31,7 @@ describe("api/auth/index",function() { describe("ensureClientSecret", function() { before(function() { - auth.init({settings:{},log:{audit:function(){}}}) + auth.init({},{}) }); it("leaves client_secret alone if not present",function(done) { var req = { @@ -85,7 +86,7 @@ describe("api/auth/index",function() { Users.init.restore(); }); 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) { resp.should.have.a.property("type","credentials"); resp.should.have.a.property("prompts"); @@ -94,14 +95,14 @@ describe("api/auth/index",function() { }}); }); it("returns login details - none", function(done) { - auth.init({settings:{},log:{audit:function(){}}}) + auth.init({},{}) auth.login(null,{json: function(resp) { resp.should.eql({}); 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) { resp.should.have.a.property("type","strategy"); 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")) + }); + }); + }); }); diff --git a/test/red/api/auth/strategies_spec.js b/test/red/api/auth/strategies_spec.js index db8e290bf..5002e8b4b 100644 --- a/test/red/api/auth/strategies_spec.js +++ b/test/red/api/auth/strategies_spec.js @@ -24,9 +24,6 @@ var Tokens = require("../../../../red/api/auth/tokens"); var Clients = require("../../../../red/api/auth/clients"); describe("api/auth/strategies", function() { - before(function() { - strategies.init({log:{audit:function(){}}}) - }); describe("Password Token Exchange", function() { var userAuthentication; afterEach(function() { diff --git a/test/red/api/editor/comms_spec.js b/test/red/api/editor/comms_spec.js index c2e9dabe2..eb9f6582c 100644 --- a/test/red/api/editor/comms_spec.js +++ b/test/red/api/editor/comms_spec.js @@ -33,6 +33,24 @@ var listenPort = 0; // use ephemeral port 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{}); + return p; } } } }); }); - 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) { request(app) .get("/credentials/known-type/n1") .expect("Content-Type",/json/) + .expect(200) .end(function(err,res) { if (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); + } + } + }) + }); }); diff --git a/test/red/api/editor/index_spec.js b/test/red/api/editor/index_spec.js index b6b93964c..7e84395f5 100644 --- a/test/red/api/editor/index_spec.js +++ b/test/red/api/editor/index_spec.js @@ -22,6 +22,10 @@ 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 log = require("../../../../red/util/log"); + + var when = require("when"); @@ -37,9 +41,7 @@ describe("api/editor/index", function() { info.init.restore(); }); it("disables the editor", function() { - var editorApp = editorApi.init({},{ - settings:{disableEditor:true} - }); + var editorApp = editorApi.init({},{disableEditor:true},{}); should.not.exist(editorApp); comms.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(); auth.needsPermission.restore(); + log.error.restore(); }); before(function() { - app = editorApi.init({},{ - log:{audit:function(){},error:function(msg){errors.push(msg)}}, - settings:{httpNodeRoot:true, httpAdminRoot: true,disableEditor:false,exportNodeSettings:function(){}}, - events:{on:function(){},removeListener:function(){}}, - isStarted: function() { return isStarted; }, - nodes: {paletteEditorEnabled: function() { return false }} + sinon.stub(log,"error",function(err) { errors.push(err)}) + app = editorApi.init({},{httpNodeRoot:true, httpAdminRoot: true,disableEditor:false,exportNodeSettings:function(){}},{ + isStarted: () => Promise.resolve(isStarted) }); }); it('serves the editor', function(done) { @@ -117,7 +117,7 @@ describe("api/editor/index", function() { done(); }); }); - // it('GET /settings', function(done) { + // it.skip('GET /settings', function(done) { // request(app).get("/settings").expect(200).end(function(err,res) { // if (err) { // return done(err); diff --git a/test/red/api/editor/library_spec.js b/test/red/api/editor/library_spec.js index 65e53e459..ef4590b69 100644 --- a/test/red/api/editor/library_spec.js +++ b/test/red/api/editor/library_spec.js @@ -16,334 +16,287 @@ 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(); + before(function() { + app = express(); + app.use(bodyParser.json()); + + app.get("/library/flows",library.getAll); + app.post(/library\/([^\/]+)\/(.*)/,library.saveEntry); + app.get(/library\/([^\/]+)(?:$|\/(.*))/,library.getEntry); + }); + after(function() { + }); + it('returns all flows', function(done) { + library.init({ + library: { + getEntries: function(opts) { + return Promise.resolve({a:1,b:2}); } - }, - 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') + .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); + done(); }); - 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('returns an error on all flows', function(done) { + library.init({ + library: { + getEntries: function(opts) { + var err = new Error("message"); + err.code = "random_error"; + err.status = 400; + var p = Promise.reject(err); + p.catch(()=>{}); + return p; + } + } }); - - 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') + .expect(400) + .end(function(err,res) { + if (err) { + return done(err); + } + 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(); }); - 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 an individual entry - flow type', function(done) { + var opts; + library.init({ + library: { + getEntry: function(_opts) { + opts = _opts; + return Promise.resolve('{"a":1,"b":2}'); + } + } }); - - 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(); - }); + request(app) + .get('/library/flows/abc') + .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); + opts.should.have.property('type','flows'); + opts.should.have.property('path','abc'); + done(); + }); + }) + it('returns a directory listing - flow type', function(done) { + var opts; + library.init({ + library: { + getEntry: function(_opts) { + opts = _opts; + return Promise.resolve({"a":1,"b":2}); + } + } }); - - it('returns 404 for non-existent entry', function(done) { - initLibrary({},{}); - request(app) - .get('/library/test/foo') - .expect(404) - .end(done); + request(app) + .get('/library/flows/abc/def') + .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); + opts.should.have.property('type','flows'); + opts.should.have.property('path','abc/def'); + done(); + }); + }) + it('returns an individual entry - non-flow type', function(done) { + var opts; + library.init({ + library: { + getEntry: function(_opts) { + opts = _opts; + return Promise.resolve('{"a":1,"b":2}'); + } + } }); - - 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(); - }); - }); + request(app) + .get('/library/non-flow/abc') + .expect(200) + .end(function(err,res) { + if (err) { + return done(err); + } + opts.should.have.property('type','non-flow'); + opts.should.have.property('path','abc'); + res.text.should.eql('{"a":1,"b":2}'); + 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}); + } + } }); + request(app) + .get('/library/non-flow/abc/def') + .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); + opts.should.have.property('type','non-flow'); + opts.should.have.property('path','abc/def'); + 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 an error on individual get', function(done) { + var opts; + library.init({ + library: { + getEntry: 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) + .get('/library/flows/123') + .expect(400) + .end(function(err,res) { + if (err) { + return done(err); + } + opts.should.have.property('type','flows'); + opts.should.have.property('path','123'); + + 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(); + }); + }); - it('returns 403 for malicious access attempt', function(done) { - request(app) - .get('/library/test/../../../../../../../../../../etc/passwd') - .expect(403) - .end(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) + .post('/library/flows/abc/def') + .expect(204) + .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) { - request(app) - .get('/library/test/..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd') - .expect(403) - .end(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) + .post('/library/non-flow/abc/def') + .expect(204) + .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) { - 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); + 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) + .post('/library/non-flow/abc/def') + .send({a:1,b:2,text:"123"}) + .expect(400) + .end(function(err,res) { + 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(); + }); }); }); diff --git a/test/red/api/editor/locales_spec.js b/test/red/api/editor/locales_spec.js index f6d1ff762..d2ee32d4a 100644 --- a/test/red/api/editor/locales_spec.js +++ b/test/red/api/editor/locales_spec.js @@ -20,6 +20,8 @@ var express = require('express'); var sinon = require('sinon'); var locales = require("../../../../red/api/editor/locales"); +var i18n = require("../../../../red/util/i18n"); + describe("api/editor/locales", function() { beforeEach(function() { @@ -30,24 +32,18 @@ describe("api/editor/locales", function() { var app; before(function() { // bit of a mess of internal workings - locales.init({ - i18n: { - i: { - lng: function() { return 'en-US'}, - setLng: function(lang,callback) { - if (callback) { - callback(); - } - } - }, - catalog: function(namespace, lang) { - return {namespace:namespace, lang:lang}; - } - } - }); + locales.init({}); + sinon.stub(i18n.i,'lng',function() { return 'en-US'}); + sinon.stub(i18n.i,'setLng',function(lang,callback) { if (callback) {callback();}}); + sinon.stub(i18n,'catalog',function(namespace, lang) {return {namespace:namespace, lang:lang};}); app = express(); 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) { request(app) .get("/locales/message-catalog") @@ -79,30 +75,31 @@ describe("api/editor/locales", function() { var app; before(function() { // bit of a mess of internal workings - locales.init({ - i18n: { - catalog: function(namespace, lang) { + sinon.stub(i18n,'catalog',function(namespace, lang) { return { "node-red": "should not return", "test-module-a-id": "test-module-a-catalog", "test-module-b-id": "test-module-b-catalog", "test-module-c-id": "test-module-c-catalog" }[namespace] - } - }, + }); + locales.init({ nodes: { - getNodeList: function() { - return [ + getNodeList: function(opts) { + return Promise.resolve([ {module:"node-red",id:"node-red-id"}, {module:"test-module-a",id:"test-module-a-id"}, {module:"test-module-b",id:"test-module-b-id"} - ]; + ]); } } }); app = express(); app.get("/locales/nodes",locales.getAllNodes); }); + after(function() { + i18n.catalog.restore(); + }) it('returns with the node catalogs', function(done) { request(app) .get("/locales/nodes") diff --git a/test/red/api/editor/settings_spec.js b/test/red/api/editor/settings_spec.js index 6f3089cf9..842c2509d 100644 --- a/test/red/api/editor/settings_spec.js +++ b/test/red/api/editor/settings_spec.js @@ -17,215 +17,103 @@ 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 app = express(); +var app; var info = require("../../../../red/api/editor/settings"); var theme = require("../../../../red/api/editor/theme"); describe("api/editor/settings", function() { - describe("settings handler", function() { - before(function() { - sinon.stub(theme,"settings",function() { return { test: 456 };}); - app = express(); - app.get("/settings",info.runtimeSettings); - }); + before(function() { + sinon.stub(theme,"settings",function() { return { test: 456 };}); + app = express(); + app.use(bodyParser.json()); + 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() { - theme.settings.restore(); - }); + after(function() { + theme.settings.restore(); + }); - 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'}} - } + it('returns the runtime settings', function(done) { + info.init({ + settings: { + getRuntimeSettings: function(opts) { + return Promise.resolve({ + a:1, + b:2 + }) } - }); - 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.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") - .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/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) - .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) + .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(); + }); }); }); diff --git a/test/red/api/editor/sshkeys_spec.js b/test/red/api/editor/sshkeys_spec.js index fbcd3ec88..a6ab51f99 100644 --- a/test/red/api/editor/sshkeys_spec.js +++ b/test/red/api/editor/sshkeys_spec.js @@ -18,83 +18,42 @@ 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 }} - }; - + settings: { + getUserKeys: function() {}, + getUserKey: function() {}, + generateUserKey: function() {}, + removeUserKey: function() {} + } + } before(function() { - auth.init(mockRuntime); + sshkeys.init(mockRuntime); app = express(); app.use(bodyParser.json()); - app.use(editorApi.init({},mockRuntime)); + app.use("/settings/user/keys", sshkeys.app()); }); - 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"); + sinon.stub(mockRuntime.settings, "getUserKeys"); + sinon.stub(mockRuntime.settings, "getUserKey"); + sinon.stub(mockRuntime.settings, "generateUserKey"); + sinon.stub(mockRuntime.settings, "removeUserKey"); }) 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(); + mockRuntime.settings.getUserKeys.restore(); + mockRuntime.settings.getUserKey.restore(); + mockRuntime.settings.generateUserKey.restore(); + mockRuntime.settings.removeUserKey.restore(); }) 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) .get("/settings/user/keys") .expect(200) @@ -118,7 +77,7 @@ describe("api/editor/sshkeys", function() { name: elem }; }); - mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve(retList)); + mockRuntime.settings.getUserKeys.returns(Promise.resolve(retList)); request(app) .get("/settings/user/keys") .expect(200) @@ -139,16 +98,16 @@ describe("api/editor/sshkeys", function() { errInstance.code = "test_code"; var p = Promise.reject(errInstance); p.catch(()=>{}); - mockRuntime.storage.projects.ssh.listSSHKeys.returns(p); + mockRuntime.settings.getUserKeys.returns(p); request(app) .get("/settings/user/keys") - .expect(400) + .expect(500) .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('code'); + res.body.code.should.be.equal(errInstance.code); res.body.should.have.property('message'); res.body.message.should.be.equal(errInstance.message); done(); @@ -156,7 +115,12 @@ describe("api/editor/sshkeys", function() { }); it('GET /settings/user/keys/ --- 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) .get("/settings/user/keys/NOT_REAL") .expect(404) @@ -168,19 +132,19 @@ describe("api/editor/sshkeys", function() { }); }); it('GET /settings/user/keys --- return Unexpected Error', function(done) { - var errInstance = new Error("Messages....."); + var errInstance = new Error(); var p = Promise.reject(errInstance); p.catch(()=>{}); - mockRuntime.storage.projects.ssh.listSSHKeys.returns(p); + mockRuntime.settings.getUserKeys.returns(p) request(app) .get("/settings/user/keys") - .expect(400) + .expect(500) .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('code'); + res.body.code.should.be.equal("unexpected_error"); res.body.should.have.property('message'); res.body.message.should.be.equal(errInstance.toString()); done(); @@ -190,7 +154,7 @@ describe("api/editor/sshkeys", function() { it('GET /settings/user/keys/ --- 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)); + mockRuntime.settings.getUserKey.returns(Promise.resolve(fileContent)); request(app) .get("/settings/user/keys/" + key_file_name) .expect(200) @@ -198,7 +162,8 @@ describe("api/editor/sshkeys", function() { if (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 }); done(); }); @@ -210,16 +175,16 @@ describe("api/editor/sshkeys", function() { errInstance.code = "test_code"; var p = Promise.reject(errInstance); p.catch(()=>{}); - mockRuntime.storage.projects.ssh.getSSHKey.returns(p); + mockRuntime.settings.getUserKey.returns(p); request(app) .get("/settings/user/keys/" + key_file_name) - .expect(400) + .expect(500) .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('code'); + res.body.code.should.be.equal(errInstance.code); res.body.should.have.property('message'); res.body.message.should.be.equal(errInstance.message); done(); @@ -231,25 +196,25 @@ describe("api/editor/sshkeys", function() { var errInstance = new Error("Messages....."); var p = Promise.reject(errInstance); p.catch(()=>{}); - mockRuntime.storage.projects.ssh.getSSHKey.returns(p); + mockRuntime.settings.getUserKey.returns(p); request(app) .get("/settings/user/keys/" + key_file_name) - .expect(400) + .expect(500) .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('code'); + res.body.code.should.be.equal("unexpected_error"); res.body.should.have.property('message'); - res.body.message.should.be.equal(errInstance.toString()); + res.body.message.should.be.equal("Messages....."); 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)); + mockRuntime.settings.generateUserKey.returns(Promise.resolve(key_file_name)); request(app) .post("/settings/user/keys") .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) { 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); + mockRuntime.settings.generateUserKey.returns(p); request(app) .post("/settings/user/keys") .send({ name: key_file_name }) - .expect(400) + .expect(500) .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('code'); + res.body.code.should.be.equal("test_code"); res.body.should.have.property('message'); res.body.message.should.be.equal(errInstance.message); done(); @@ -308,26 +255,26 @@ describe("api/editor/sshkeys", function() { var errInstance = new Error("Messages....."); var p = Promise.reject(errInstance); p.catch(()=>{}); - mockRuntime.storage.projects.ssh.generateSSHKey.returns(p); + mockRuntime.settings.generateUserKey.returns(p); request(app) .post("/settings/user/keys") .send({ name: key_file_name }) - .expect(400) + .expect(500) .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('code'); + res.body.code.should.be.equal("unexpected_error"); res.body.should.have.property('message'); - res.body.message.should.be.equal(errInstance.toString()); + res.body.message.should.be.equal("Messages....."); done(); }); }); it('DELETE /settings/user/keys/ --- success', function(done) { var key_file_name = "test_key"; - mockRuntime.storage.projects.ssh.deleteSSHKey.returns(Promise.resolve(true)); + mockRuntime.settings.removeUserKey.returns(Promise.resolve(true)); request(app) .delete("/settings/user/keys/" + key_file_name) .expect(204) @@ -346,16 +293,16 @@ describe("api/editor/sshkeys", function() { errInstance.code = "test_code"; var p = Promise.reject(errInstance); p.catch(()=>{}); - mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p); + mockRuntime.settings.removeUserKey.returns(p); request(app) .delete("/settings/user/keys/" + key_file_name) - .expect(400) + .expect(500) .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('code'); + res.body.code.should.be.equal("test_code"); res.body.should.have.property('message'); res.body.message.should.be.equal(errInstance.message); done(); @@ -367,18 +314,18 @@ describe("api/editor/sshkeys", function() { var errInstance = new Error("Messages....."); var p = Promise.reject(errInstance); p.catch(()=>{}); - mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p); + mockRuntime.settings.removeUserKey.returns(p); request(app) .delete("/settings/user/keys/" + key_file_name) - .expect(400) + .expect(500) .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('code'); + res.body.code.should.be.equal("unexpected_error"); res.body.should.have.property('message'); - res.body.message.should.be.equal(errInstance.toString()); + res.body.message.should.be.equal('Messages.....'); done(); }); }); diff --git a/test/red/api/editor/theme_spec.js b/test/red/api/editor/theme_spec.js index 513a8e912..88d265875 100644 --- a/test/red/api/editor/theme_spec.js +++ b/test/red/api/editor/theme_spec.js @@ -33,7 +33,7 @@ describe("api/editor/theme", function() { fs.statSync.restore(); }); it("applies the default theme", function() { - var result = theme.init({settings:{},version:function() { return '123.456'}}); + var result = theme.init({}); should.not.exist(result); var context = theme.context(); @@ -43,13 +43,12 @@ describe("api/editor/theme", function() { context.should.have.a.property("header"); context.header.should.have.a.property("title","Node-RED"); 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()); }); it("picks up custom theme", function() { - theme.init({settings:{ + theme.init({ editorTheme: { page: { title: "Test Page Title", @@ -84,7 +83,7 @@ describe("api/editor/theme", function() { image: "/absolute/path/to/login/page/big/image" // a 256x256 image } } - }}); + }); theme.app(); diff --git a/test/red/api/editor/ui_spec.js b/test/red/api/editor/ui_spec.js index 9ebfa8185..0704adcd4 100644 --- a/test/red/api/editor/ui_spec.js +++ b/test/red/api/editor/ui_spec.js @@ -20,8 +20,6 @@ var express = require("express"); var fs = require("fs"); var path = require("path"); -var EventEmitter = require('events').EventEmitter; -var events = new EventEmitter(); var ui = require("../../../../red/api/editor/ui"); @@ -30,10 +28,13 @@ describe("api/editor/ui", function() { before(function() { ui.init({ - events:events, nodes: { - getNodeIconPath: function(module,icon) { - return path.resolve(__dirname+'/../../../../public/icons/arrow-in.png'); + getIcon: function(opts) { + return new Promise(function(resolve,reject) { + fs.readFile(path.resolve(__dirname+'/../../../../public/icons/arrow-in.png'), function(err,data) { + resolve(data); + }) + }); } } }); diff --git a/test/red/api/index_spec.js b/test/red/api/index_spec.js index 103949dea..a97ec3843 100644 --- a/test/red/api/index_spec.js +++ b/test/red/api/index_spec.js @@ -23,7 +23,6 @@ var fs = require("fs"); var path = require("path"); var api = require("../../../red/api"); -var apiUtil = require("../../../red/api/util"); var apiAuth = require("../../../red/api/auth"); var apiEditor = require("../../../red/api/editor"); var apiAdmin = require("../../../red/api/admin"); @@ -31,7 +30,6 @@ var apiAdmin = require("../../../red/api/admin"); describe("api/index", function() { var beforeEach = function() { - sinon.stub(apiUtil,"init",function(){}); sinon.stub(apiAuth,"init",function(){}); sinon.stub(apiEditor,"init",function(){ var app = express(); @@ -48,7 +46,6 @@ describe("api/index", function() { }); }; var afterEach = function() { - apiUtil.init.restore(); apiAuth.init.restore(); apiAuth.login.restore(); apiEditor.init.restore(); @@ -59,18 +56,14 @@ describe("api/index", function() { afterEach(afterEach); it("does not setup admin api if httpAdminRoot is false", function(done) { - api.init({},{ - settings: { httpAdminRoot: false } - }); + api.init({},{ httpAdminRoot: false },{},{}); should.not.exist(api.adminApp); done(); }); describe('initalises admin api without adminAuth', function(done) { before(function() { beforeEach(); - api.init({},{ - settings: { } - }); + api.init({},{},{},{}); }); after(afterEach); it('exposes the editor',function(done) { @@ -87,9 +80,7 @@ describe("api/index", function() { describe('initalises admin api without editor', function(done) { before(function() { beforeEach(); - api.init({},{ - settings: { disableEditor: true } - }); + api.init({},{ disableEditor: true },{},{}); }); after(afterEach); it('does not expose the editor',function(done) { diff --git a/test/red/api/util_spec.js b/test/red/api/util_spec.js index fb5151c14..bb6b27521 100644 --- a/test/red/api/util_spec.js +++ b/test/red/api/util_spec.js @@ -15,11 +15,17 @@ **/ var should = require("should"); +var sinon = require("sinon"); var request = require('supertest'); var express = require('express'); 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("errorHandler", function() { var loggedError = null; @@ -27,17 +33,8 @@ describe("api/util", function() { var app; before(function() { app = express(); - apiUtil.init({ - log:{ - error: function(msg) { - loggedError = msg; - }, - audit: function(event) { - loggedEvent = event; - } - }, - i18n:{} - }) + sinon.stub(log,'error',function(msg) {loggedError = msg;}); + sinon.stub(log,'audit',function(event) {loggedEvent = event;}); app.get("/tooLarge", function(req,res) { var err = new Error(); err.message = "request entity too large"; @@ -49,6 +46,10 @@ describe("api/util", function() { throw err; },apiUtil.errorHandler) }); + after(function() { + log.error.restore(); + log.audit.restore(); + }) beforeEach(function() { loggedError = null; loggedEvent = null; @@ -91,11 +92,13 @@ describe("api/util", function() { }) describe('determineLangFromHeaders', function() { + var oldDefaultLang; before(function() { - apiUtil.init({ - log:{}, - i18n:{defaultLang:"en-US"} - }); + oldDefaultLang = i18n.defaultLang; + i18n.defaultLang = "en-US"; + }) + after(function() { + i18n.defaultLang = oldDefaultLang; }) it('returns the default lang if non provided', function() { apiUtil.determineLangFromHeaders(null).should.eql("en-US"); diff --git a/test/red/runtime-api/comms_spec.js b/test/red/runtime-api/comms_spec.js index 1db07365b..8cc252b90 100644 --- a/test/red/runtime-api/comms_spec.js +++ b/test/red/runtime-api/comms_spec.js @@ -14,6 +14,229 @@ * limitations under the License. **/ +var should = require("should"); +var sinon = require("sinon"); + +var comms = require("../../../red/runtime-api/comms"); + 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); + }); + }) + }); diff --git a/test/red/runtime-api/flows_spec.js b/test/red/runtime-api/flows_spec.js index ddbab2b9d..502eaddca 100644 --- a/test/red/runtime-api/flows_spec.js +++ b/test/red/runtime-api/flows_spec.js @@ -17,3 +17,275 @@ describe("runtime-api/flows", 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(); + }); + }); + +}); + +*/ diff --git a/test/red/runtime-api/index_spec.js b/test/red/runtime-api/index_spec.js index 650f96da1..c5e1865e2 100644 --- a/test/red/runtime-api/index_spec.js +++ b/test/red/runtime-api/index_spec.js @@ -14,6 +14,41 @@ * limitations under the License. **/ +var should = require("should"); +var sinon = require("sinon"); + +var index = require("../../../red/runtime-api/index"); + + 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); + }) + }); diff --git a/test/red/runtime-api/library_spec.js b/test/red/runtime-api/library_spec.js index f996ea487..7c41a32de 100644 --- a/test/red/runtime-api/library_spec.js +++ b/test/red/runtime-api/library_spec.js @@ -17,3 +17,342 @@ describe("runtime-api/library", 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); + }); + + }); +}); + +*/ diff --git a/test/red/runtime-api/nodes_spec.js b/test/red/runtime-api/nodes_spec.js index a22b49081..80237816e 100644 --- a/test/red/runtime-api/nodes_spec.js +++ b/test/red/runtime-api/nodes_spec.js @@ -17,3 +17,829 @@ describe("runtime-api/nodes", 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 ""; + } + }, + i18n: { + determineLangFromHeaders: function(){} + } + }); + request(app) + .get('/nodes') + .set('Accept', 'text/html') + .expect(200) + .expect("") + .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":""}[id]; + } + }, + i18n: { + determineLangFromHeaders: function(){} + } + }); + request(app) + .get('/nodes/node-red/123') + .set('Accept', 'text/html') + .expect(200) + .expect("") + .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(); + }); + }); + }); +}); + +*/ diff --git a/test/red/runtime-api/settings_spec.js b/test/red/runtime-api/settings_spec.js index 77c2090e8..668ae96a3 100644 --- a/test/red/runtime-api/settings_spec.js +++ b/test/red/runtime-api/settings_spec.js @@ -17,3 +17,569 @@ describe("runtime-api/settings", 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/ --- 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/ --- 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/ --- 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/ --- 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/ --- 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/ --- 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/ --- 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(); + }); + }); +}); +*/ diff --git a/test/red/runtime/index_spec.js b/test/red/runtime/index_spec.js index 59ab9c3dc..499dfecfc 100644 --- a/test/red/runtime/index_spec.js +++ b/test/red/runtime/index_spec.js @@ -14,7 +14,6 @@ * limitations under the License. **/ var should = require("should"); -var when = require("when"); var sinon = require("sinon"); var path = require("path"); @@ -45,6 +44,7 @@ describe("runtime", function() { log: sinon.stub(), warn: sinon.stub(), info: sinon.stub(), + trace: sinon.stub(), metric: sinon.stub().returns(!!metrics), _: function() { return "abc"} }, @@ -90,11 +90,11 @@ describe("runtime", function() { var redNodesLoadFlows; var redNodesStartFlows; 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() {}); - redNodesLoad = sinon.stub(redNodes,"load", function() {return when.resolve()}); + redNodesLoad = sinon.stub(redNodes,"load", function() {return Promise.resolve()}); 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() {}); }); afterEach(function() { @@ -114,7 +114,7 @@ describe("runtime", function() { ].filter(cb); }); 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"); runtime.start().then(function() { // console.log.restore(); @@ -143,9 +143,9 @@ describe("runtime", function() { { module:"node-red",enabled:true,loaded:false,types:["typeC","typeD"]} // missing ].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(); - 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"); runtime.start().then(function() { console.log.restore(); @@ -172,7 +172,7 @@ describe("runtime", function() { ].filter(cb); }); 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"); runtime.start().then(function() { console.log.restore(); @@ -190,7 +190,7 @@ describe("runtime", function() { var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []}); 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"); runtime.start().then(function() { console.log.restore(); diff --git a/test/red/runtime/library/index_spec.js b/test/red/runtime/library/index_spec.js index bfb550835..882d4d75a 100644 --- a/test/red/runtime/library/index_spec.js +++ b/test/red/runtime/library/index_spec.js @@ -14,6 +14,167 @@ * 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() { - 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); + }) + + }); }); diff --git a/test/red/runtime/nodes/Node_spec.js b/test/red/runtime/nodes/Node_spec.js index 80bae7654..c2df3a8ec 100644 --- a/test/red/runtime/nodes/Node_spec.js +++ b/test/red/runtime/nodes/Node_spec.js @@ -110,7 +110,7 @@ describe('Node', function() { p.then(function() { callbacksClosed.should.eql(3); testdone(); - }).otherwise(function(e) { + }).catch(function(e) { testdone(e); }); }); diff --git a/test/red/runtime/nodes/credentials_spec.js b/test/red/runtime/nodes/credentials_spec.js index b4a324bcc..75a9fc3cc 100644 --- a/test/red/runtime/nodes/credentials_spec.js +++ b/test/red/runtime/nodes/credentials_spec.js @@ -427,7 +427,7 @@ describe('red/runtime/nodes/credentials', function() { // credentials.dirty().should.be.true(); // should.not.exist(credentials.get("node")); done(); - }).otherwise(function(err) { + }).catch(function(err) { err.should.have.property('code','credentials_load_failed'); done(); }); @@ -443,7 +443,7 @@ describe('red/runtime/nodes/credentials', function() { // credentials.dirty().should.be.true(); // should.not.exist(credentials.get("node")); done(); - }).otherwise(function(err) { + }).catch(function(err) { err.should.have.property('code','credentials_load_failed'); done(); }); diff --git a/test/red/runtime/nodes/flows/index_spec.js b/test/red/runtime/nodes/flows/index_spec.js index 68f896b58..c39085187 100644 --- a/test/red/runtime/nodes/flows/index_spec.js +++ b/test/red/runtime/nodes/flows/index_spec.js @@ -524,7 +524,7 @@ describe('flows/index', function() { ] }).then(function() { done(new Error('failed to reject duplicate node id')); - }).otherwise(function(err) { + }).catch(function(err) { done(); }) }); @@ -559,7 +559,7 @@ describe('flows/index', function() { createdFlows.should.have.lengthOf(3); createdFlows[2].should.eql(id); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }) }); diff --git a/test/red/runtime/nodes/index_spec.js b/test/red/runtime/nodes/index_spec.js index 10f309227..f36918a10 100644 --- a/test/red/runtime/nodes/index_spec.js +++ b/test/red/runtime/nodes/index_spec.js @@ -19,7 +19,6 @@ var fs = require('fs-extra'); var path = require('path'); var when = require("when"); var sinon = require('sinon'); -console.log(__dirname); var index = require("../../../../red/runtime/nodes/index"); var flows = require("../../../../red/runtime/nodes/flows"); 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('d',"bar"); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -93,7 +92,7 @@ describe("red/nodes/index", function() { // console.log(index.getFlows()); should.deepEqual(testFlows, index.getFlows().flows); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); @@ -258,7 +257,7 @@ describe("red/nodes/index", function() { info.should.eql(randomNodeInfo); done(); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -273,7 +272,7 @@ describe("red/nodes/index", function() { }).should.throw(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -288,7 +287,7 @@ describe("red/nodes/index", function() { }).should.throw(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -341,7 +340,7 @@ describe("red/nodes/index", function() { }).should.throw(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -356,7 +355,7 @@ describe("red/nodes/index", function() { }).should.throw(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); diff --git a/test/red/runtime/nodes/registry/index_spec.js b/test/red/runtime/nodes/registry/index_spec.js index 30cd8685e..590d60040 100644 --- a/test/red/runtime/nodes/registry/index_spec.js +++ b/test/red/runtime/nodes/registry/index_spec.js @@ -60,7 +60,7 @@ describe('red/nodes/registry/index', function() { registry.addModule("foo").then(function(info) { info.should.eql("info"); done(); - }).otherwise(function(err) { done(err); }); + }).catch(function(err) { done(err); }); }); it('rejects if loader rejects', function(done) { stubs.push(sinon.stub(loader,"addModule",function(module) { @@ -71,7 +71,7 @@ describe('red/nodes/registry/index', function() { })); registry.addModule("foo").then(function(info) { done(new Error("unexpected resolve")); - }).otherwise(function(err) { + }).catch(function(err) { err.should.eql("error"); done(); }) @@ -90,7 +90,7 @@ describe('red/nodes/registry/index', function() { typeRegistry.enableNodeSet.called.should.be.true(); ns.should.have.a.property('id','node-set'); done(); - }).otherwise(function(err) { done(err); }); + }).catch(function(err) { done(err); }); }); 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('loaded',true); done(); - }).otherwise(function(err) { done(err); }); + }).catch(function(err) { done(err); }); }); }); diff --git a/test/red/runtime/nodes/registry/installer_spec.js b/test/red/runtime/nodes/registry/installer_spec.js index dd9c782f4..78566a3d3 100644 --- a/test/red/runtime/nodes/registry/installer_spec.js +++ b/test/red/runtime/nodes/registry/installer_spec.js @@ -83,7 +83,7 @@ describe('nodes/registry/installer', function() { return ee; }); - installer.installModule("this_wont_exist").otherwise(function(err) { + installer.installModule("this_wont_exist").catch(function(err) { err.should.have.property("code",404); 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); done(); }); @@ -116,7 +116,7 @@ describe('nodes/registry/installer', function() { 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'); done(); }); @@ -135,7 +135,7 @@ describe('nodes/registry/installer', function() { installer.installModule("this_wont_exist").then(function() { done(new Error("Unexpected success")); - }).otherwise(function(err) { + }).catch(function(err) { done(); }); }); @@ -160,7 +160,7 @@ describe('nodes/registry/installer', function() { // commsMessages[0].topic.should.equal("node/added"); // commsMessages[0].msg.should.eql(nodeInfo.nodes); done(); - }).otherwise(function(err) { + }).catch(function(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")); installer.installModule(resourcesDir).then(function() { done(new Error("Unexpected success")); - }).otherwise(function(err) { + }).catch(function(err) { if (err.hasOwnProperty("code")) { err.code.should.eql(404); done(); @@ -199,7 +199,7 @@ describe('nodes/registry/installer', function() { installer.installModule(resourcesDir).then(function(info) { info.should.eql(nodeInfo); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -228,7 +228,7 @@ describe('nodes/registry/installer', function() { installer.uninstallModule("this_wont_exist").then(function() { done(new Error("Unexpected success")); - }).otherwise(function(err) { + }).catch(function(err) { done(); }); }); @@ -252,7 +252,7 @@ describe('nodes/registry/installer', function() { // commsMessages[0].topic.should.equal("node/removed"); // commsMessages[0].msg.should.eql(nodeInfo); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); diff --git a/test/red/runtime/nodes/registry/loader_spec.js b/test/red/runtime/nodes/registry/loader_spec.js index 87e1c060b..4f9ddf8a7 100644 --- a/test/red/runtime/nodes/registry/loader_spec.js +++ b/test/red/runtime/nodes/registry/loader_spec.js @@ -69,7 +69,7 @@ describe("red/nodes/registry/loader",function() { loader.load("foo",true).then(function() { registry.saveNodeList.called.should.be.true(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }) }); @@ -118,7 +118,7 @@ describe("red/nodes/registry/loader",function() { nodes.registerType.lastCall.args[1].should.eql('test-node-1'); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -169,7 +169,7 @@ describe("red/nodes/registry/loader",function() { nodes.registerType.secondCall.args[1].should.eql('test-node-multiple-1b'); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -219,7 +219,7 @@ describe("red/nodes/registry/loader",function() { nodes.registerType.lastCall.args[1].should.eql('test-node-2'); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -267,7 +267,7 @@ describe("red/nodes/registry/loader",function() { nodes.registerType.calledOnce.should.be.false(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -313,7 +313,7 @@ describe("red/nodes/registry/loader",function() { nodes.registerType.calledOnce.should.be.false(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -360,7 +360,7 @@ describe("red/nodes/registry/loader",function() { nodes.registerType.calledOnce.should.be.false(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -379,7 +379,7 @@ describe("red/nodes/registry/loader",function() { 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.addModule("test-module").otherwise(function(err) { + loader.addModule("test-module").catch(function(err) { err.code.should.eql("module_already_loaded"); done(); }); @@ -390,7 +390,7 @@ describe("red/nodes/registry/loader",function() { 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.addModule("test-module").otherwise(function(err) { + loader.addModule("test-module").catch(function(err) { err.message.should.eql("failure"); done(); }); @@ -441,7 +441,7 @@ describe("red/nodes/registry/loader",function() { nodes.registerType.calledOnce.should.be.true(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -477,7 +477,7 @@ describe("red/nodes/registry/loader",function() { registry.addNodeSet.called.should.be.false(); nodes.registerType.called.should.be.false(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -498,7 +498,7 @@ describe("red/nodes/registry/loader",function() { node.enabled.should.be.false(); nodes.registerType.called.should.be.false(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -517,7 +517,7 @@ describe("red/nodes/registry/loader",function() { node.err.toString().should.eql("Error: fail to require (line:1)"); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); diff --git a/test/red/runtime/nodes/registry/registry_spec.js b/test/red/runtime/nodes/registry/registry_spec.js index ce761c012..c6f3e19af 100644 --- a/test/red/runtime/nodes/registry/registry_spec.js +++ b/test/red/runtime/nodes/registry/registry_spec.js @@ -292,7 +292,7 @@ describe("red/nodes/registry/registry",function() { it('rejects when settings unavailable',function(done) { typeRegistry.init(stubSettings({},false,{})); typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1"); - typeRegistry.saveNodeList().otherwise(function(err) { + typeRegistry.saveNodeList().catch(function(err) { done(); }); }); @@ -312,7 +312,7 @@ describe("red/nodes/registry/registry",function() { nn.should.not.have.property('id'); } done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); diff --git a/test/red/runtime/settings_spec.js b/test/red/runtime/settings_spec.js index d3b5f35ed..4766e85e7 100644 --- a/test/red/runtime/settings_spec.js +++ b/test/red/runtime/settings_spec.js @@ -14,7 +14,6 @@ * limitations under the License. **/ var should = require("should"); -var when = require("when"); var settings = require("../../../red/runtime/settings"); @@ -86,12 +85,12 @@ describe("red/settings", function() { var saveCount = 0; var storage = { getSettings: function() { - return when.resolve({globalA:789}); + return Promise.resolve({globalA:789}); }, saveSettings: function(settings) { saveCount++; savedSettings = settings; - return when.resolve(); + return Promise.resolve(); } } settings.init(userSettings); @@ -115,7 +114,7 @@ describe("red/settings", function() { done(); }); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); diff --git a/test/red/runtime/storage/localfilesystem/index_spec.js b/test/red/runtime/storage/localfilesystem/index_spec.js index ed2b4e010..9921581b2 100644 --- a/test/red/runtime/storage/localfilesystem/index_spec.js +++ b/test/red/runtime/storage/localfilesystem/index_spec.js @@ -47,7 +47,7 @@ describe('storage/localfilesystem', function() { fs.existsSync(path.join(userDir,"lib")).should.be.true(); fs.existsSync(path.join(userDir,"lib",'flows')).should.be.true(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -70,7 +70,7 @@ describe('storage/localfilesystem', function() { } finally { process.env.NODE_RED_HOME = oldNRH; } - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -96,7 +96,7 @@ describe('storage/localfilesystem', function() { process.env.NODE_RED_HOME = oldNRH; process.env.NODE_HOMEPATH = oldHOMEPATH; } - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -124,7 +124,7 @@ describe('storage/localfilesystem', function() { process.env.HOME = oldHOME; process.env.HOMEPATH = oldHOMEPATH; } - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -155,7 +155,7 @@ describe('storage/localfilesystem', function() { process.env.HOMEPATH = oldHOMEPATH; process.env.USERPROFILE = oldUSERPROFILE; } - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -168,10 +168,10 @@ describe('storage/localfilesystem', function() { localfilesystem.getFlows().then(function(flows) { flows.should.eql([]); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -186,10 +186,10 @@ describe('storage/localfilesystem', function() { localfilesystem.getFlows().then(function(flows) { flows.should.eql([]); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -208,11 +208,11 @@ describe('storage/localfilesystem', function() { localfilesystem.getFlows().then(function(flows) { flows.should.eql(testFlow); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); },50); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -230,13 +230,13 @@ describe('storage/localfilesystem', function() { localfilesystem.getFlows().then(function(flows) { flows.should.eql(testFlow); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -257,13 +257,13 @@ describe('storage/localfilesystem', function() { localfilesystem.getFlows().then(function(flows) { flows.should.eql(testFlow); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -278,13 +278,13 @@ describe('storage/localfilesystem', function() { localfilesystem.getFlows().then(function(flows) { flows.should.eql(testFlow); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -298,11 +298,11 @@ describe('storage/localfilesystem', function() { fs.fsync.callCount.should.be.greaterThan(0); fs.fsync.restore(); done(); - }).otherwise(function(err) { + }).catch(function(err) { fs.fsync.restore(); done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -321,10 +321,10 @@ describe('storage/localfilesystem', function() { fs.fsync.callCount.should.be.greaterThan(0); fs.fsync.restore(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -358,14 +358,14 @@ describe('storage/localfilesystem', function() { content2.should.not.equal(backupContent); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); @@ -382,10 +382,10 @@ describe('storage/localfilesystem', function() { localfilesystem.getCredentials().then(function(creds) { creds.should.eql({}); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -406,13 +406,13 @@ describe('storage/localfilesystem', function() { localfilesystem.getCredentials().then(function(creds) { creds.should.eql(credentials); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -437,10 +437,10 @@ describe('storage/localfilesystem', function() { fs.existsSync(credFile).should.be.true(); fs.existsSync(credFileBackup).should.be.true(); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -463,13 +463,13 @@ describe('storage/localfilesystem', function() { localfilesystem.getCredentials().then(function(creds) { creds.should.eql(credentials); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); diff --git a/test/red/runtime/storage/localfilesystem/sessions_spec.js b/test/red/runtime/storage/localfilesystem/sessions_spec.js index aa2322d3c..cbfc3cf84 100644 --- a/test/red/runtime/storage/localfilesystem/sessions_spec.js +++ b/test/red/runtime/storage/localfilesystem/sessions_spec.js @@ -38,7 +38,7 @@ describe('storage/localfilesystem/sessions', function() { localfilesystemSessions.getSessions().then(function(sessions) { sessions.should.eql({}); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -51,7 +51,7 @@ describe('storage/localfilesystem/sessions', function() { localfilesystemSessions.getSessions().then(function(sessions) { sessions.should.eql({}); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -69,10 +69,10 @@ describe('storage/localfilesystem/sessions', function() { localfilesystemSessions.getSessions().then(function(_sessions) { _sessions.should.eql(sessions); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); diff --git a/test/red/runtime/storage/localfilesystem/settings_spec.js b/test/red/runtime/storage/localfilesystem/settings_spec.js index 3785b7ce0..56fd10cf9 100644 --- a/test/red/runtime/storage/localfilesystem/settings_spec.js +++ b/test/red/runtime/storage/localfilesystem/settings_spec.js @@ -39,7 +39,7 @@ describe('storage/localfilesystem/settings', function() { localfilesystemSettings.getSettings().then(function(settings) { settings.should.eql({}); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -52,7 +52,7 @@ describe('storage/localfilesystem/settings', function() { localfilesystemSettings.getSettings().then(function(settings) { settings.should.eql({}); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); }); @@ -70,10 +70,10 @@ describe('storage/localfilesystem/settings', function() { localfilesystemSettings.getSettings().then(function(_settings) { _settings.should.eql(settings); done(); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); - }).otherwise(function(err) { + }).catch(function(err) { done(err); }); });