From b581e33611c65fe89331113ef86991d8738f8c56 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 25 Apr 2019 11:32:09 +0100 Subject: [PATCH] Update runtime apis to support multiple libraries --- .../@node-red/editor-api/lib/editor/index.js | 5 +- .../editor-api/lib/editor/library.js | 22 +- .../editor-client/src/js/ui/clipboard.js | 84 ++-- .../editor-client/src/js/ui/library.js | 28 +- .../@node-red/registry/lib/library.js | 18 +- .../@node-red/runtime/lib/api/library.js | 39 +- .../@node-red/runtime/lib/library/examples.js | 101 +++++ .../@node-red/runtime/lib/library/index.js | 86 ++-- .../@node-red/runtime/lib/library/local.js | 37 ++ .../editor-api/lib/editor/library_spec.js | 77 +--- .../@node-red/registry/lib/library_spec.js | 2 +- .../@node-red/runtime/lib/api/library_spec.js | 392 +----------------- .../runtime/lib/library/examples_spec.js | 138 ++++++ .../runtime/lib/library/index_spec.js | 144 +++---- .../runtime/lib/library/local_spec.js | 93 +++++ 15 files changed, 553 insertions(+), 713 deletions(-) create mode 100644 packages/node_modules/@node-red/runtime/lib/library/examples.js create mode 100644 packages/node_modules/@node-red/runtime/lib/library/local.js create mode 100644 test/unit/@node-red/runtime/lib/library/examples_spec.js create mode 100644 test/unit/@node-red/runtime/lib/library/local_spec.js diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/index.js b/packages/node_modules/@node-red/editor-api/lib/editor/index.js index dbf4c18f2..d29ab0b1b 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/index.js @@ -93,9 +93,8 @@ 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); + editorApp.get(/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry); + editorApp.post(/library\/([^\/]+)\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry); // Credentials diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/library.js b/packages/node_modules/@node-red/editor-api/lib/editor/library.js index e8b09424a..47a41bb7b 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/library.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/library.js @@ -25,23 +25,12 @@ module.exports = { init: function(_runtimeAPI) { runtimeAPI = _runtimeAPI; }, - - getAll: function(req,res) { - var opts = { - user: req.user, - type: 'flows' - } - runtimeAPI.library.getEntries(opts).then(function(result) { - res.json(result); - }).catch(function(err) { - apiUtils.rejectHandler(req,res,err); - }); - }, getEntry: function(req,res) { var opts = { user: req.user, - type: req.params[0], - path: req.params[1]||"" + library: req.params[0], + type: req.params[1], + path: req.params[2]||"" } runtimeAPI.library.getEntry(opts).then(function(result) { if (typeof result === "string") { @@ -62,8 +51,9 @@ module.exports = { saveEntry: function(req,res) { var opts = { user: req.user, - type: req.params[0], - path: req.params[1]||"" + library: req.params[0], + type: req.params[1], + path: req.params[2]||"" } // TODO: horrible inconsistencies between flows and all other types if (opts.type === "flows") { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js index fd339c5c3..5ace86456 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js @@ -78,7 +78,7 @@ RED.clipboard = (function() { var filename = $("#clipboard-dialog-tab-library-name").val().trim(); var saveFlow = function() { $.ajax({ - url:'library/flows/'+selectedPath.path + filename, + url:'library/'+selectedPath.library+'/'+selectedPath.type+'/'+selectedPath.path + filename, type: "POST", data: flowToExport, contentType: "application/json; charset=utf-8" @@ -139,7 +139,7 @@ RED.clipboard = (function() { } else { var selectedPath = libraryBrowser.getSelected(); if (selectedPath.path) { - $.get('library/flows/'+selectedPath.path, function(data) { + $.get('library/'+selectedPath.library+'/'+selectedPath.type+'/'+selectedPath.path, function(data) { RED.view.importNodes(data,addNewFlow); }); } @@ -609,59 +609,39 @@ RED.clipboard = (function() { $("#clipboard-dialog-download").show(); } - function transformFlowList(list,label,root,includeExamples) { - var result = { - icon: root===""?"fa fa-archive":'fa fa-folder', - label: label, - path: root - }; - result.children = []; - - if (list.f) { - list.f.forEach(function(f) { - result.children.push({ - icon: 'fa fa-file-o', - label: f, - path: root+f - }); - }); - } - if (list.d) { - for (var l in list.d) { - if (list.d.hasOwnProperty(l)) { - if (root+l !== "_examples_") { - result.children.push(transformFlowList(list.d[l], l,root+l+"/",includeExamples)) - } else if (includeExamples) { - result._examples = transformFlowList(list.d[l], l,root+l+"/",includeExamples) - } - } - } - } - result.children.sort(function(A,B){ - if (A.children && !B.children) { - return -1; - } else if (!A.children && B.children) { - return 1; - } else { - return A.label.localeCompare(B.label); - } - }); - return result; - } function loadFlowLibrary(browser,includeExamples) { - $.getJSON("library/flows", function(data) { - var listing = [transformFlowList(data,RED._("library.types.local"),"",includeExamples)]; - listing[0].expanded = true; - if (includeExamples && listing[0]._examples) { - var examples = listing[0]._examples; - delete listing[0]._examples; - examples.label = RED._("library.types.examples"); - examples.icon = "fa fa-archive"; - listing.unshift(examples) + var listing = []; + if (includeExamples) { + listing.push({ + library: "_examples_", + type: "flows", + icon: 'fa fa-hdd-o', + label: RED._("library.types.examples"), + path: "", + children: function(item,done) { + RED.library.loadLibraryFolder("_examples_","flows","",function(children) { + item.children = children; + done(children); + }) + } + }) + } + listing.push({ + library: "local", + type: "flows", + icon: 'fa fa-hdd-o', + label: RED._("library.types.local"), + path: "", + expanded: true, + children: function(item,done) { + RED.library.loadLibraryFolder("local","flows","",function(children) { + item.children = children; + done(children); + }) } - browser.data(listing); - }); + }) + browser.data(listing); } function hideDropTarget() { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js index 7c8b5d811..b78a31a8b 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js @@ -58,7 +58,6 @@ RED.library = (function() { } var filename = $("#node-dialog-library-save-filename").val().trim() var selectedPath = saveLibraryBrowser.getSelected(); - console.log(selectedPath); if (!selectedPath.children) { selectedPath = selectedPath.parent; } @@ -75,7 +74,7 @@ RED.library = (function() { data.text = activeLibrary.editor.getValue(); var saveFlow = function() { $.ajax({ - url:"library/"+activeLibrary.url+'/'+selectedPath.path + filename, + url:"library/"+selectedPath.library+'/'+selectedPath.type+'/'+selectedPath.path + filename, type: "POST", data: JSON.stringify(data), contentType: "application/json; charset=utf-8" @@ -89,8 +88,6 @@ RED.library = (function() { } }); } - console.log(filename); - console.log(selectedPath); if (selectedPath.children) { var exists = false; selectedPath.children.forEach(function(f) { @@ -125,16 +122,18 @@ RED.library = (function() { } } - function loadLibraryFolder(url,root,done) { - $.getJSON("library/"+url+"/"+root,function(data) { + function loadLibraryFolder(library,type,root,done) { + $.getJSON("library/"+library+"/"+type+"/"+root,function(data) { var items = data.map(function(d) { if (typeof d === "string") { return { + library: library, + type: type, icon: 'fa fa-folder', label: d, path: root+d+"/", children: function(item,done) { - loadLibraryFolder(url,root+d+"/", function(children) { + loadLibraryFolder(library,type,root+d+"/", function(children) { item.children = children; // TODO: should this be done by treeList for us done(children); }) @@ -142,6 +141,8 @@ RED.library = (function() { }; } else { return { + library: library, + type: type, icon: 'fa fa-file-o', label: d.fn, path: root+d.fn, @@ -209,9 +210,11 @@ RED.library = (function() { $('#node-input-'+options.type+'-menu-open-library').click(function(e) { activeLibrary = options; - loadLibraryFolder(options.url, "", function(items) { + loadLibraryFolder("local",options.url, "", function(items) { var listing = [{ - icon: 'fa fa-archive', + library: "local", + type: options.url, + icon: 'fa fa-hdd-o', label: RED._("library.types.local"), path: "", expanded: true, @@ -255,7 +258,7 @@ RED.library = (function() { } $("#node-dialog-library-save-filename").attr("value",filename+".js"); - loadLibraryFolder(options.url, "", function(items) { + loadLibraryFolder("local",options.url, "", function(items) { var listing = [{ icon: 'fa fa-archive', label: RED._("library.types.local"), @@ -483,7 +486,7 @@ RED.library = (function() { var table = $("#node-dialog-library-load-preview-details-table").empty(); selectedLibraryItem = file.props; if (file && file.label && !file.children) { - $.get("library/"+activeLibrary.url+"/"+file.path, function(data) { + $.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) { //TODO: nls + sanitize var propRow = $('Type').appendTo(table); $(propRow.children()[1]).text(activeLibrary.type); @@ -516,6 +519,7 @@ RED.library = (function() { }, create: createUI, createBrowser:createBrowser, - export: exportFlow + export: exportFlow, + loadLibraryFolder: loadLibraryFolder } })(); diff --git a/packages/node_modules/@node-red/registry/lib/library.js b/packages/node_modules/@node-red/registry/lib/library.js index 9f7c6e8b2..1eea6ed56 100644 --- a/packages/node_modules/@node-red/registry/lib/library.js +++ b/packages/node_modules/@node-red/registry/lib/library.js @@ -16,7 +16,6 @@ var fs = require('fs'); var fspath = require('path'); -var when = require('when'); var runtime; @@ -24,7 +23,7 @@ var exampleRoots = {}; var exampleFlows = null; function getFlowsFromPath(path) { - return when.promise(function(resolve,reject) { + return new Promise(function(resolve,reject) { var result = {}; fs.readdir(path,function(err,files) { var promises = []; @@ -37,11 +36,11 @@ function getFlowsFromPath(path) { promises.push(getFlowsFromPath(fullPath)); } else if (/\.json$/.test(file)){ validFiles.push(file); - promises.push(when.resolve(file.split(".")[0])) + promises.push(Promise.resolve(file.split(".")[0])) } }) var i=0; - when.all(promises).then(function(results) { + Promise.all(promises).then(function(results) { results.forEach(function(r) { if (typeof r === 'string') { result.f = result.f||[]; @@ -62,21 +61,20 @@ function getFlowsFromPath(path) { function addNodeExamplesDir(module,path) { exampleRoots[module] = path; return getFlowsFromPath(path).then(function(result) { - exampleFlows = exampleFlows||{d:{}}; - exampleFlows.d[module] = result; + exampleFlows = exampleFlows||{}; + exampleFlows[module] = result; }); } function removeNodeExamplesDir(module) { delete exampleRoots[module]; - if (exampleFlows && exampleFlows.d) { - delete exampleFlows.d[module]; + if (exampleFlows) { + delete exampleFlows[module]; } - if (exampleFlows && Object.keys(exampleFlows.d).length === 0) { + if (exampleFlows && Object.keys(exampleFlows).length === 0) { exampleFlows = null; } } - function init() { exampleRoots = {}; exampleFlows = null; diff --git a/packages/node_modules/@node-red/runtime/lib/api/library.js b/packages/node_modules/@node-red/runtime/lib/api/library.js index 6f6571463..31037858b 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/library.js +++ b/packages/node_modules/@node-red/runtime/lib/api/library.js @@ -29,6 +29,7 @@ var api = module.exports = { * Gets an entry from the library. * @param {Object} opts * @param {User} opts.user - the user calling the api + * @param {String} opts.library - the library * @param {String} opts.type - the type of entry * @param {String} opts.path - the path of the entry * @return {Promise} - resolves when complete @@ -36,12 +37,12 @@ var api = module.exports = { */ getEntry: function(opts) { return new Promise(function(resolve,reject) { - runtime.library.getEntry(opts.type,opts.path).then(function(result) { - runtime.log.audit({event: "library.get",type:opts.type,path:opts.path}); + runtime.library.getEntry(opts.library,opts.type,opts.path).then(function(result) { + runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path}); return resolve(result); }).catch(function(err) { if (err) { - runtime.log.warn(runtime.log._("api.library.error-load-entry",{path:opts.path,message:err.toString()})); + runtime.log.warn(runtime.log._("api.library.error-load-entry",{library:opts.library,type:opts.type,path:opts.path,message:err.toString()})); if (err.code === 'forbidden') { err.status = 403; return reject(err); @@ -50,10 +51,10 @@ var api = module.exports = { } else { err.status = 400; } - runtime.log.audit({event: "library.get",type:opts.type,path:opts.path,error:err.code}); + runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path,error:err.code}); return reject(err); } - runtime.log.audit({event: "library.get",type:opts.type,error:"not_found"}); + runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,error:"not_found"}); var error = new Error(); error.code = "not_found"; error.status = 404; @@ -66,6 +67,7 @@ var api = module.exports = { * Saves an entry to the library * @param {Object} opts * @param {User} opts.user - the user calling the api + * @param {String} opts.library - the library * @param {String} opts.type - the type of entry * @param {String} opts.path - the path of the entry * @param {Object} opts.meta - any meta data associated with the entry @@ -75,7 +77,7 @@ var api = module.exports = { */ saveEntry: function(opts) { return new Promise(function(resolve,reject) { - runtime.library.saveEntry(opts.type,opts.path,opts.meta,opts.body).then(function() { + runtime.library.saveEntry(opts.library,opts.type,opts.path,opts.meta,opts.body).then(function() { runtime.log.audit({event: "library.set",type:opts.type,path:opts.path}); return resolve(); }).catch(function(err) { @@ -91,30 +93,5 @@ var api = module.exports = { return reject(error); }); }) - }, - /** - * Returns a complete listing of all entries of a given type in the library. - * @param {Object} opts - * @param {User} opts.user - the user calling the api - * @param {String} opts.type - the type of entry - * @return {Promise} - the entry listing - * @memberof @node-red/runtime_library - */ - getEntries: function(opts) { - return new Promise(function(resolve,reject) { - if (opts.type !== 'flows') { - return reject(new Error("API only supports flows")); - - } - runtime.storage.getAllFlows().then(function(flows) { - runtime.log.audit({event: "library.get.all",type:"flow"}); - var examples = runtime.nodes.getNodeExampleFlows(); - if (examples) { - flows.d = flows.d||{}; - flows.d._examples_ = examples; - } - return resolve(flows); - }); - }) } } diff --git a/packages/node_modules/@node-red/runtime/lib/library/examples.js b/packages/node_modules/@node-red/runtime/lib/library/examples.js new file mode 100644 index 000000000..bde277a5f --- /dev/null +++ b/packages/node_modules/@node-red/runtime/lib/library/examples.js @@ -0,0 +1,101 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var fs = require('fs'); + +var runtime; + +function init(_runtime) { + runtime = _runtime; +} + +function getEntry(type,path) { + var examples = runtime.nodes.getNodeExampleFlows(); + var result = []; + if (path === "") { + return Promise.resolve(Object.keys(examples)); + } else { + path = path.replace(/\/$/,""); + var parts = path.split("/"); + var module = parts.shift(); + if (module[0] === "@") { + module = module+"/"+parts.shift(); + } + if (examples.hasOwnProperty(module)) { + examples = examples[module]; + examples = parts.reduce(function(ex,k) { + if (ex) { + if (ex.d && ex.d[k]) { + return ex.d[k] + } + if (ex.f && ex.f.indexOf(k) > -1) { + return runtime.nodes.getNodeExampleFlowPath(module,parts.join("/")); + } + } else { + return null; + } + },examples); + + if (!examples) { + return new Promise(function (resolve,reject) { + var error = new Error("not_found"); + error.code = "not_found"; + return reject(error); + }); + } else if (typeof examples === 'string') { + return new Promise(function(resolve,reject) { + try { + fs.readFile(examples,'utf8',function(err, data) { + runtime.log.audit({event: "library.get",library:"_examples",type:"flow",path:path}); + if (err) { + return reject(err); + } + return resolve(data); + }) + } catch(err) { + return reject(err); + } + }); + } else { + if (examples.d) { + for (var d in examples.d) { + if (examples.d.hasOwnProperty(d)) { + result.push(d); + } + } + } + if (examples.f) { + examples.f.forEach(function(f) { + result.push({fn:f}) + }) + } + return Promise.resolve(result); + } + } else { + return new Promise(function (resolve,reject) { + var error = new Error("not_found"); + error.code = "not_found"; + return reject(error); + }); + } + } +} + +module.exports = { + name: '_examples_', + init: init, + getEntry: getEntry +} diff --git a/packages/node_modules/@node-red/runtime/lib/library/index.js b/packages/node_modules/@node-red/runtime/lib/library/index.js index cc8cd1509..8a4e6518e 100644 --- a/packages/node_modules/@node-red/runtime/lib/library/index.js +++ b/packages/node_modules/@node-red/runtime/lib/library/index.js @@ -14,18 +14,22 @@ * limitations under the License. **/ -var fs = require('fs'); -var fspath = require('path'); -var runtime; var knownTypes = {}; -var storage; +var libraries = {}; + + +function init(runtime) { + knownTypes = { + 'flows': 'node-red' + }; + + libraries["_examples_"] = require("./examples"); + libraries["_examples_"].init(runtime); + libraries["local"] = require("./local"); + libraries["local"].init(runtime); -function init(_runtime) { - runtime = _runtime; - storage = runtime.storage; - knownTypes = {}; } function registerType(id,type) { @@ -37,66 +41,34 @@ function registerType(id,type) { knownTypes[type] = id; } -// 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)) { - throw new Error(`Unknown library type '${type}'`); - } - return storage.getLibraryEntry(type,path); +function getEntry(library,type,path) { + if (!knownTypes.hasOwnProperty(type)) { + throw new Error(`Unknown library type '${type}'`); + } + if (libraries.hasOwnProperty(library)) { + return libraries[library].getEntry(type,path); } else { - return new Promise(function(resolve,reject) { - if (path.indexOf("_examples_/") === 0) { - var m = /^_examples_\/(@.*?\/[^\/]+|[^\/]+)\/(.*)$/.exec(path); - if (m) { - var module = m[1]; - var entryPath = m[2]; - var fullPath = runtime.nodes.getNodeExampleFlowPath(module,entryPath); - if (fullPath) { - try { - fs.readFile(fullPath,'utf8',function(err, data) { - runtime.log.audit({event: "library.get",type:"flow",path:path}); - if (err) { - return reject(err); - } - return resolve(data); - }) - } catch(err) { - return reject(err); - } - return; - } - } - // 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)); - } - }); + throw new Error(`Unknown library '${library}'`); } } -function saveEntry(type,path,meta,body) { - if (type !== 'flows') { - if (!knownTypes.hasOwnProperty(type)) { - throw new Error(`Unknown library type '${type}'`); +function saveEntry(library,type,path,meta,body) { + if (!knownTypes.hasOwnProperty(type)) { + throw new Error(`Unknown library type '${type}'`); + } + if (libraries.hasOwnProperty(library)) { + if (libraries[library].hasOwnProperty("saveEntry")) { + return libraries[library].saveEntry(type,path,meta,body); + } else { + throw new Error(`Library '${library}' is read-only`); } - return storage.saveLibraryEntry(type,path,meta,body); } else { - return storage.saveFlow(path,body); + throw new Error(`Unknown library '${library}'`); } } module.exports = { init: init, register: registerType, - // getAllEntries: getAllEntries, getEntry: getEntry, saveEntry: saveEntry diff --git a/packages/node_modules/@node-red/runtime/lib/library/local.js b/packages/node_modules/@node-red/runtime/lib/library/local.js new file mode 100644 index 000000000..454e800ca --- /dev/null +++ b/packages/node_modules/@node-red/runtime/lib/library/local.js @@ -0,0 +1,37 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var runtime; +var storage; + +function init(_runtime) { + runtime = _runtime; + storage = runtime.storage; +} + +function getEntry(type,path) { + return storage.getLibraryEntry(type,path); +} +function saveEntry(type,path,meta,body) { + return storage.saveLibraryEntry(type,path,meta,body); +} + +module.exports = { + name: 'local', + init: init, + getEntry: getEntry, + saveEntry: saveEntry +} diff --git a/test/unit/@node-red/editor-api/lib/editor/library_spec.js b/test/unit/@node-red/editor-api/lib/editor/library_spec.js index 891305199..4a2c281b7 100644 --- a/test/unit/@node-red/editor-api/lib/editor/library_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/library_spec.js @@ -31,60 +31,11 @@ describe("api/editor/library", function() { before(function() { app = express(); app.use(bodyParser.json()); - - app.get("/library/flows",library.getAll); - app.post(/library\/([^\/]+)\/(.*)/,library.saveEntry); - app.get(/library\/([^\/]+)(?:$|\/(.*))/,library.getEntry); + app.get(/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,library.getEntry); + app.post(/library\/([^\/]+)\/([^\/]+)\/(.*)/,library.saveEntry); }); after(function() { }); - it('returns all flows', function(done) { - library.init({ - library: { - getEntries: function(opts) { - return Promise.resolve({a:1,b:2}); - } - } - }); - 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(); - }); - }) - 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; - } - } - }); - 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(); - }); - }); it('returns an individual entry - flow type', function(done) { var opts; @@ -97,7 +48,7 @@ describe("api/editor/library", function() { } }); request(app) - .get('/library/flows/abc') + .get('/library/local/flows/abc') .expect(200) .end(function(err,res) { if (err) { @@ -105,6 +56,7 @@ describe("api/editor/library", function() { } res.body.should.have.property('a',1); res.body.should.have.property('b',2); + opts.should.have.property('library','local'); opts.should.have.property('type','flows'); opts.should.have.property('path','abc'); done(); @@ -121,7 +73,7 @@ describe("api/editor/library", function() { } }); request(app) - .get('/library/flows/abc/def') + .get('/library/local/flows/abc/def') .expect(200) .end(function(err,res) { if (err) { @@ -129,6 +81,7 @@ describe("api/editor/library", function() { } res.body.should.have.property('a',1); res.body.should.have.property('b',2); + opts.should.have.property('library','local'); opts.should.have.property('type','flows'); opts.should.have.property('path','abc/def'); done(); @@ -145,12 +98,13 @@ describe("api/editor/library", function() { } }); request(app) - .get('/library/non-flow/abc') + .get('/library/local/non-flow/abc') .expect(200) .end(function(err,res) { if (err) { return done(err); } + opts.should.have.property('library','local'); opts.should.have.property('type','non-flow'); opts.should.have.property('path','abc'); res.text.should.eql('{"a":1,"b":2}'); @@ -168,7 +122,7 @@ describe("api/editor/library", function() { } }); request(app) - .get('/library/non-flow/abc/def') + .get('/library/local/non-flow/abc/def') .expect(200) .end(function(err,res) { if (err) { @@ -176,6 +130,7 @@ describe("api/editor/library", function() { } res.body.should.have.property('a',1); res.body.should.have.property('b',2); + opts.should.have.property('library','local'); opts.should.have.property('type','non-flow'); opts.should.have.property('path','abc/def'); done(); @@ -198,12 +153,13 @@ describe("api/editor/library", function() { } }); request(app) - .get('/library/flows/123') + .get('/library/local/flows/123') .expect(400) .end(function(err,res) { if (err) { return done(err); } + opts.should.have.property('library','local'); opts.should.have.property('type','flows'); opts.should.have.property('path','123'); @@ -227,13 +183,14 @@ describe("api/editor/library", function() { } }); request(app) - .post('/library/flows/abc/def') + .post('/library/local/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('library','local'); opts.should.have.property('type','flows'); opts.should.have.property('path','abc/def'); opts.should.have.property('meta',{}); @@ -253,13 +210,14 @@ describe("api/editor/library", function() { } }); request(app) - .post('/library/non-flow/abc/def') + .post('/library/local/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('library','local'); opts.should.have.property('type','non-flow'); opts.should.have.property('path','abc/def'); opts.should.have.property('meta',{a:1,b:2}); @@ -284,7 +242,7 @@ describe("api/editor/library", function() { } }); request(app) - .post('/library/non-flow/abc/def') + .post('/library/local/non-flow/abc/def') .send({a:1,b:2,text:"123"}) .expect(400) .end(function(err,res) { @@ -292,6 +250,7 @@ describe("api/editor/library", function() { return done(err); } opts.should.have.property('type','non-flow'); + opts.should.have.property('library','local'); opts.should.have.property('path','abc/def'); res.body.should.have.property('code'); diff --git a/test/unit/@node-red/registry/lib/library_spec.js b/test/unit/@node-red/registry/lib/library_spec.js index d8999c2db..2e0e7e99a 100644 --- a/test/unit/@node-red/registry/lib/library_spec.js +++ b/test/unit/@node-red/registry/lib/library_spec.js @@ -38,7 +38,7 @@ describe("library api", function() { library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() { try { var flows = library.getExampleFlows(); - flows.should.deepEqual({"d":{"test-module":{"f":["one"]}}}); + flows.should.deepEqual({"test-module":{"f":["one"]}}); var examplePath = library.getExampleFlowPath('test-module','one'); examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json')) diff --git a/test/unit/@node-red/runtime/lib/api/library_spec.js b/test/unit/@node-red/runtime/lib/api/library_spec.js index 8f99580b9..3fa5291d2 100644 --- a/test/unit/@node-red/runtime/lib/api/library_spec.js +++ b/test/unit/@node-red/runtime/lib/api/library_spec.js @@ -38,7 +38,7 @@ describe("runtime-api/library", function() { library.init({ log: mockLog, library: { - getEntry: function(type,path) { + getEntry: function(library, type,path) { if (type === "known") { return Promise.resolve("known"); } else if (type === "forbidden") { @@ -67,13 +67,13 @@ describe("runtime-api/library", function() { }) }) it("returns a known entry", function(done) { - library.getEntry({type: "known", path: "/abc"}).then(function(result) { + library.getEntry({library: "local",type: "known", path: "/abc"}).then(function(result) { result.should.eql("known") done(); }).catch(done) }) it("rejects a forbidden entry", function(done) { - library.getEntry({type: "forbidden", path: "/abc"}).then(function(result) { + library.getEntry({library: "local",type: "forbidden", path: "/abc"}).then(function(result) { done(new Error("did not reject")); }).catch(function(err) { err.should.have.property("code","forbidden"); @@ -82,7 +82,7 @@ describe("runtime-api/library", function() { }).catch(done) }) it("rejects an unknown entry", function(done) { - library.getEntry({type: "not_found", path: "/abc"}).then(function(result) { + library.getEntry({library: "local",type: "not_found", path: "/abc"}).then(function(result) { done(new Error("did not reject")); }).catch(function(err) { err.should.have.property("code","not_found"); @@ -91,7 +91,7 @@ describe("runtime-api/library", function() { }).catch(done) }) it("rejects a blank (unknown) entry", function(done) { - library.getEntry({type: "blank", path: "/abc"}).then(function(result) { + library.getEntry({library: "local",type: "blank", path: "/abc"}).then(function(result) { done(new Error("did not reject")); }).catch(function(err) { err.should.have.property("code","not_found"); @@ -100,7 +100,7 @@ describe("runtime-api/library", function() { }).catch(done) }) it("rejects unexpected error", function(done) { - library.getEntry({type: "error", path: "/abc"}).then(function(result) { + library.getEntry({library: "local",type: "error", path: "/abc"}).then(function(result) { done(new Error("did not reject")); }).catch(function(err) { err.should.have.property("status",400); @@ -114,7 +114,7 @@ describe("runtime-api/library", function() { library.init({ log: mockLog, library: { - saveEntry: function(type,path,meta,body) { + saveEntry: function(library,type,path,meta,body) { opts = {type,path,meta,body}; if (type === "known") { return Promise.resolve(); @@ -137,7 +137,7 @@ describe("runtime-api/library", function() { }) it("saves an entry", function(done) { - library.saveEntry({type: "known", path: "/abc", meta: {a:1}, body:"123"}).then(function() { + library.saveEntry({library: "local",type: "known", path: "/abc", meta: {a:1}, body:"123"}).then(function() { opts.should.have.property("type","known"); opts.should.have.property("path","/abc"); opts.should.have.property("meta",{a:1}); @@ -146,7 +146,7 @@ describe("runtime-api/library", function() { }).catch(done) }) it("rejects a forbidden entry", function(done) { - library.saveEntry({type: "forbidden", path: "/abc", meta: {a:1}, body:"123"}).then(function() { + library.saveEntry({library: "local",type: "forbidden", path: "/abc", meta: {a:1}, body:"123"}).then(function() { done(new Error("did not reject")); }).catch(function(err) { err.should.have.property("code","forbidden"); @@ -155,7 +155,7 @@ describe("runtime-api/library", function() { }).catch(done) }) it("rejects an unknown entry", function(done) { - library.saveEntry({type: "not_found", path: "/abc", meta: {a:1}, body:"123"}).then(function() { + library.saveEntry({library: "local",type: "not_found", path: "/abc", meta: {a:1}, body:"123"}).then(function() { done(new Error("did not reject")); }).catch(function(err) { err.should.have.property("status",400); @@ -163,377 +163,5 @@ describe("runtime-api/library", function() { }).catch(done) }) }) - describe("getEntries", function() { - var opts; - before(function() { - library.init({ - log: mockLog, - storage: { - getAllFlows: function() { - return Promise.resolve({a:1}); - } - }, - nodes: { - getNodeExampleFlows: function() { - return {b:2}; - } - } - }); - }); - it("returns all flows", function(done) { - library.getEntries({type:"flows"}).then(function(result) { - result.should.eql({a:1,d:{_examples_:{b:2}}}); - done(); - }).catch(done) - }); - it("fails for non-flows (currently)", function(done) { - library.getEntries({type:"functions"}).then(function(result) { - done(new Error("did not reject")); - }).catch(function(err) { - done(); - }).catch(done) - }) - }) - }); - - -/* - -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', - fspath.resolve('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', - fspath.resolve('@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/unit/@node-red/runtime/lib/library/examples_spec.js b/test/unit/@node-red/runtime/lib/library/examples_spec.js new file mode 100644 index 000000000..16d917c7b --- /dev/null +++ b/test/unit/@node-red/runtime/lib/library/examples_spec.js @@ -0,0 +1,138 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var sinon = require("sinon"); +var fs = require("fs"); + +var NR_TEST_UTILS = require("nr-test-utils"); +var examplesLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/examples") + +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/examples", function() { + describe("getEntry", function() { + before(function() { + examplesLibrary.init({ + log: mockLog, + storage: { + getLibraryEntry: function(type,path) { + return Promise.resolve({type,path}); + }, + getFlow: function(path) { + return Promise.resolve({path}); + } + }, + nodes: { + getNodeExampleFlows: function() { + return { + "test-module": { + f: ["abc"] + }, + "@scope/test-module": { + f: ["abc","throw"] + } + + } + }, + 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 ('returns a flow example entry', function(done) { + examplesLibrary.getEntry("flows","test-module/abc").then(function(result) { + result.should.eql("Example flow result"); + done(); + }).catch(done); + }); + + it ('returns a flow example listing - top level', function(done) { + examplesLibrary.getEntry("flows","").then(function(result) { + result.should.eql([ 'test-module', '@scope/test-module' ]) + done(); + }).catch(done); + }); + it ('returns a flow example listing - in module', function(done) { + examplesLibrary.getEntry("flows","test-module").then(function(result) { + result.should.eql([{ fn: 'abc' }]) + done(); + }).catch(done); + }); + it ('returns a flow example listing - in scoped module', function(done) { + examplesLibrary.getEntry("flows","@scope/test-module").then(function(result) { + result.should.eql([{ fn: 'abc' }, {fn: 'throw'}]) + done(); + }).catch(done); + }); + it ('returns a flow example entry from scoped module', function(done) { + examplesLibrary.getEntry("flows","@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) { + examplesLibrary.getEntry("flows","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) { + examplesLibrary.getEntry("flows","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) { + examplesLibrary.getEntry("flows","test-module/throw").then(function(result) { + done(new Error("No error thrown")) + }).catch(function(err) { + done(); + }); + }); + }); +}); diff --git a/test/unit/@node-red/runtime/lib/library/index_spec.js b/test/unit/@node-red/runtime/lib/library/index_spec.js index bef42d824..ac2608a18 100644 --- a/test/unit/@node-red/runtime/lib/library/index_spec.js +++ b/test/unit/@node-red/runtime/lib/library/index_spec.js @@ -16,10 +16,11 @@ var should = require("should"); var sinon = require("sinon"); -var fs = require("fs"); var NR_TEST_UTILS = require("nr-test-utils"); var library = NR_TEST_UTILS.require("@node-red/runtime/lib/library/index") +var localLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/local") +var examplesLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/examples") var mockLog = { log: sinon.stub(), @@ -34,6 +35,36 @@ var mockLog = { describe("runtime/library", function() { + before(function() { + sinon.stub(localLibrary,"getEntry",function(type,path) { + return Promise.resolve({ + library: "local", + type:type, + path:path + }) + }); + sinon.stub(localLibrary,"saveEntry",function(type, path, meta, body) { + return Promise.resolve({ + library: "local", + type:type, + path:path, + meta:meta, + body:body + }) + }); + sinon.stub(examplesLibrary,"getEntry",function(type,path) { + return Promise.resolve({ + library: "_examples_", + type:type, + path:path + }) + }); + }); + after(function() { + localLibrary.getEntry.restore(); + localLibrary.saveEntry.restore(); + examplesLibrary.getEntry.restore(); + }) describe("register", function() { // it("throws error for duplicate type", function() { // library.init({}); @@ -43,47 +74,19 @@ describe("runtime/library", function() { }) 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)) - } - }) + library.init({}); }); - after(function() { - fs.readFile.restore(); - }) it('throws error for unregistered type', function() { - should(()=>{library.getEntry("unknown","/abc")} ).throw(); + should(()=>{library.getEntry("local","unknown","/abc")} ).throw(); + }); + it('throws error for unknown library', function() { + should(()=>{library.getEntry("unknown","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) { + library.getEntry("local","test-type","/abc").then(function(result) { + result.should.have.property("library","local") result.should.have.property("type","test-type") result.should.have.property("path","/abc") done(); @@ -91,76 +94,37 @@ describe("runtime/library", function() { }); it ('returns a flow entry', function(done) { - library.getEntry("flows","/abc").then(function(result) { + library.getEntry("local","flows","/abc").then(function(result) { + result.should.have.property("library","local") 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"); + library.getEntry("_examples_","flows","/test-module/abc").then(function(result) { + result.should.have.property("library","_examples_") + result.should.have.property("path","/test-module/abc") 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; - } - } - }); + library.init({}); + }); + it('throws error for unknown library', function() { + should(()=>{library.saveEntry("unknown","unknown","/abc",{id:"meta"},{id:"body"})} ).throw(); }); it('throws error for unregistered type', function() { - should(()=>{library.saveEntry("unknown","/abc",{id:"meta"},{id:"body"})} ).throw(); + should(()=>{library.saveEntry("local","unknown","/abc",{id:"meta"},{id:"body"})} ).throw(); + }); + it('throws error for save to readonly library', function() { + should(()=>{library.saveEntry("_examples_","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) { + library.saveEntry('local','flows','/abc',{id:"meta"},{id:"body"}).then(function(result) { result.should.have.property("path","/abc"); result.should.have.property("body",{id:"body"}); done(); @@ -168,7 +132,7 @@ describe("runtime/library", function() { }) 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) { + library.saveEntry('local','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"}); diff --git a/test/unit/@node-red/runtime/lib/library/local_spec.js b/test/unit/@node-red/runtime/lib/library/local_spec.js new file mode 100644 index 000000000..965e5c87a --- /dev/null +++ b/test/unit/@node-red/runtime/lib/library/local_spec.js @@ -0,0 +1,93 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var sinon = require("sinon"); + +var NR_TEST_UTILS = require("nr-test-utils"); +var localLibrary = NR_TEST_UTILS.require("@node-red/runtime/lib/library/local") + +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/local", function() { + + describe("getEntry", function() { + before(function() { + localLibrary.init({ + log: mockLog, + storage: { + getLibraryEntry: function(type,path) { + return Promise.resolve({type,path}); + } + } + }); + }); + + it('returns a registered non-flow entry', function(done) { + localLibrary.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) { + localLibrary.getEntry("flows","/abc").then(function(result) { + result.should.have.property("path","/abc") + done(); + }).catch(done); + }); + }); + + describe("saveEntry", function() { + before(function() { + localLibrary.init({ + log: mockLog, + storage: { + saveLibraryEntry: function(type, path, meta, body) { + return Promise.resolve({type,path,meta,body}) + } + } + }); + }); + it('saves a flow entry', function(done) { + localLibrary.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) { + localLibrary.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); + }) + + }); +});