diff --git a/test/unit/@node-red/runtime/lib/api/context_spec.js b/test/unit/@node-red/runtime/lib/api/context_spec.js index 175138d25..db604f2a9 100644 --- a/test/unit/@node-red/runtime/lib/api/context_spec.js +++ b/test/unit/@node-red/runtime/lib/api/context_spec.js @@ -14,8 +14,200 @@ * limitations under the License. **/ -var NR_TEST_UTILS = require("nr-test-utils"); +var should = require("should"); +var sinon = require("sinon"); + +var NR_TEST_UTILS = require("nr-test-utils"); +var context = NR_TEST_UTILS.require("@node-red/runtime/lib/api/context") + +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"} +}) + +var mockContext = function(contents) { + return { + get: function(key,store,callback) { + if (contents.hasOwnProperty(store) && contents[store].hasOwnProperty(key)) { + callback(null,contents[store][key]); + } else { + callback(null,undefined); + } + }, + keys: function(store,callback) { + if (contents.hasOwnProperty(store)) { + callback(null,Object.keys(contents[store])); + } else { + callback("err store"); + } + } + } +} describe("runtime-api/context", function() { - it.skip("NEEDS TESTS WRITING",function() {}); + describe("getValue", function() { + var contexts = { + global: mockContext({ default: {abc:111}, file: {abc:222}}), + flow1: mockContext({ default: {abc:333}, file: {abc:444}}) + } + var nodeContext = mockContext({ default: {abc:555}, file: {abc:666}}) + + beforeEach(function() { + context.init({ + nodes: { + listContextStores: function() { + return { default: 'default', stores: [ 'default', 'file' ] } + }, + getContext: function(id) { + return contexts[id] + }, + getNode: function(id) { + if (id === 'known') { + return { + context: function() { return nodeContext } + } + } else { + return null; + } + } + }, + settings: { + functionGlobalContext: { + fgc:1234 + } + }, + log: mockLog() + }) + }); + + it('gets global value of default store', function() { + return context.getValue({ + scope: 'global', + id: undefined, + store: undefined, // use default + key: 'abc' + }).then(function(result) { + result.should.have.property('msg','111'); + result.should.have.property('format','number'); + }) + }) + it('gets global value of specified store', function() { + return context.getValue({ + scope: 'global', + id: undefined, + store: 'file', + key: 'abc' + }).then(function(result) { + result.should.have.property('msg','222'); + result.should.have.property('format','number'); + }) + }) + it('gets flow value of default store', function() { + return context.getValue({ + scope: 'flow', + id: 'flow1', + store: undefined, // use default + key: 'abc' + }).then(function(result) { + result.should.have.property('msg','333'); + result.should.have.property('format','number'); + }) + }) + it('gets flow value of specified store', function() { + return context.getValue({ + scope: 'flow', + id: 'flow1', + store: 'file', + key: 'abc' + }).then(function(result) { + result.should.have.property('msg','444'); + result.should.have.property('format','number'); + }) + }) + it('gets node value of default store', function() { + return context.getValue({ + scope: 'node', + id: 'known', + store: undefined, // use default + key: 'abc' + }).then(function(result) { + result.should.have.property('msg','555'); + result.should.have.property('format','number'); + }) + }) + it('gets node value of specified store', function() { + return context.getValue({ + scope: 'node', + id: 'known', + store: 'file', + key: 'abc' + }).then(function(result) { + result.should.have.property('msg','666'); + result.should.have.property('format','number'); + }) + }) + + it('404s for unknown store', function(done) { + context.getValue({ + scope: 'global', + id: undefined, + store: 'unknown', + key: 'abc' + }).then(function(result) { + done("getValue for unknown store should not resolve"); + }).catch(function(err) { + err.should.have.property('code','not_found') + err.should.have.property('status',404); + done(); + }) + }) + + + it('gets all global value properties', function() { + return context.getValue({ + scope: 'global', + id: undefined, + store: undefined, // use default + key: undefined, // + }).then(function(result) { + result.should.eql({ + default: { abc: { msg: '111', format: 'number' } }, + file: { abc: { msg: '222', format: 'number' } } + }); + }) + }) + it('gets all flow value properties', function() { + return context.getValue({ + scope: 'flow', + id: 'flow1', + store: undefined, // use default + key: undefined, // + }).then(function(result) { + result.should.eql({ + default: { abc: { msg: '333', format: 'number' } }, + file: { abc: { msg: '444', format: 'number' } } + }); + }) + }) + it('gets all node value properties', function() { + return context.getValue({ + scope: 'node', + id: 'known', + store: undefined, // use default + key: undefined, // + }).then(function(result) { + result.should.eql({ + default: { abc: { msg: '555', format: 'number' } }, + file: { abc: { msg: '666', format: 'number' } } + }); + }) + }) + + }) }); diff --git a/test/unit/@node-red/runtime/lib/exec_spec.js b/test/unit/@node-red/runtime/lib/exec_spec.js index e0369e07c..1e37b7cc1 100644 --- a/test/unit/@node-red/runtime/lib/exec_spec.js +++ b/test/unit/@node-red/runtime/lib/exec_spec.js @@ -16,7 +16,123 @@ var should = require("should"); var sinon = require("sinon"); var path = require("path"); +var events = require("events"); + + +var child_process = require('child_process'); var NR_TEST_UTILS = require("nr-test-utils"); var exec = NR_TEST_UTILS.require("@node-red/runtime/lib/exec"); + +describe("runtime/exec", function() { + var logEvents; + var mockProcess; + + beforeEach(function() { + var logEventHandler = new events.EventEmitter(); + logEvents = []; + logEventHandler.on('event-log', function(ev) { + logEvents.push(ev); + }); + exec.init({events:logEventHandler}); + + mockProcess = new events.EventEmitter(); + mockProcess.stdout = new events.EventEmitter(); + mockProcess.stderr = new events.EventEmitter(); + sinon.stub(child_process,'spawn',function(command,args,options) { + mockProcess._args = {command,args,options}; + return mockProcess; + }); + }); + + afterEach(function() { + if (child_process.spawn.restore) { + child_process.spawn.restore(); + } + }); + + it("runs command and resolves on success - no emit", function(done) { + var command = "cmd"; + var args = [1,2,3]; + var opts = { a: true }; + exec.run(command,args,opts).then(function(result) { + command.should.eql(mockProcess._args.command); + args.should.eql(mockProcess._args.args); + opts.should.eql(mockProcess._args.options); + logEvents.length.should.eql(0); + result.code.should.eql(0); + result.stdout.should.eql("123"); + result.stderr.should.eql("abc"); + done(); + }).catch(done); + + mockProcess.stdout.emit('data',"1"); + mockProcess.stderr.emit('data',"a"); + mockProcess.stderr.emit('data',"b"); + mockProcess.stdout.emit('data',"2"); + mockProcess.stdout.emit('data',"3"); + mockProcess.stderr.emit('data',"c"); + mockProcess.emit('close',0); + }); + + it("runs command and resolves on success - emit", function(done) { + var command = "cmd"; + var args = [1,2,3]; + var opts = { a: true }; + exec.run(command,args,opts,true).then(function(result) { + logEvents.length.should.eql(8); + done(); + }).catch(done); + + mockProcess.stdout.emit('data',"1"); + mockProcess.stderr.emit('data',"a"); + mockProcess.stderr.emit('data',"b"); + mockProcess.stdout.emit('data',"2"); + mockProcess.stdout.emit('data',"3"); + mockProcess.stderr.emit('data',"c"); + mockProcess.emit('close',0); + }) + + it("runs command and rejects on error - close", function(done) { + var command = "cmd"; + var args = [1,2,3]; + var opts = { a: true }; + exec.run(command,args,opts).then(function() { + done("Command should have rejected"); + }).catch(function(result) { + result + result.code.should.eql(123); + result.stdout.should.eql("123"); + result.stderr.should.eql("abc"); + done(); + }).catch(done); + + mockProcess.stdout.emit('data',"1"); + mockProcess.stderr.emit('data',"a"); + mockProcess.stderr.emit('data',"b"); + mockProcess.stdout.emit('data',"2"); + mockProcess.stdout.emit('data',"3"); + mockProcess.stderr.emit('data',"c"); + mockProcess.emit('close',123); + }) + + it("runs command and rejects on error - error", function(done) { + var command = "cmd"; + var args = [1,2,3]; + var opts = { a: true }; + exec.run(command,args,opts).then(function() { + done("Command should have rejected"); + }).catch(function(result) { + result + result.code.should.eql(456); + result.stdout.should.eql(""); + result.stderr.should.eql("test-error"); + done(); + }).catch(done); + + mockProcess.emit('error',"test-error"); + mockProcess.emit('close',456); + }) + +}); diff --git a/test/unit/@node-red/runtime/lib/settings_spec.js b/test/unit/@node-red/runtime/lib/settings_spec.js index 65d808fbb..51c190fea 100644 --- a/test/unit/@node-red/runtime/lib/settings_spec.js +++ b/test/unit/@node-red/runtime/lib/settings_spec.js @@ -19,7 +19,7 @@ var NR_TEST_UTILS = require("nr-test-utils"); var settings = NR_TEST_UTILS.require("@node-red/runtime/lib/settings"); -describe("red/settings", function() { +describe("runtime/settings", function() { afterEach(function() { settings.reset(); @@ -240,4 +240,94 @@ describe("red/settings", function() { safeSettings.should.have.property("httpRequestColor", "yellow"); }); + + it('delete global setting', function() { + // read-only + var localSettings = {a:1}; + // read-write + var globalSettings = {b:2}; + var storage = { + getSettings: function() { + return Promise.resolve(globalSettings); + }, + saveSettings: function() { + return Promise.resolve(); + } + } + settings.init(localSettings); + return settings.load(storage).then(function() { + settings.get('a').should.eql(1); + settings.get('b').should.eql(2); + return settings.delete('b') + }).then(function() { + should.not.exist(settings.get('b')); + }) + }); + + it('refused to delete local setting', function(done) { + // read-only + var localSettings = {a:1}; + // read-write + var globalSettings = {b:2}; + var storage = { + getSettings: function() { + return Promise.resolve(globalSettings); + } + } + settings.init(localSettings); + settings.load(storage).then(function() { + settings.get('a').should.eql(1); + settings.get('b').should.eql(2); + try { + settings.delete('a'); + return done("Did not throw error"); + } catch(err) { + // expected + } + done(); + }).catch(done) + }); + + + it('get user settings', function() { + var userSettings = { + admin: {a:1} + } + var storage = { + getSettings: function() { + return Promise.resolve({a:1,users:userSettings}); + } + } + settings.init(userSettings); + return settings.load(storage).then(function() { + var result = settings.getUserSettings('admin'); + result.should.eql(userSettings.admin); + // Check it has been cloned + result.should.not.equal(userSettings.admin); + }) + }) + it('set user settings', function() { + var userSettings = { + admin: {a:1} + } + var savedSettings; + var storage = { + getSettings: function() { + return Promise.resolve({c:3,users:userSettings}); + }, + saveSettings: function(s) { + savedSettings = s; + return Promise.resolve(); + } + } + settings.init(userSettings); + return settings.load(storage).then(function() { + return settings.setUserSettings('admin',{b:2}) + }).then(function() { + savedSettings.should.have.property("c",3); + savedSettings.should.have.property('users'); + savedSettings.users.should.eql({admin:{b:2}}) + }) + }) + });