diff options
Diffstat (limited to 'dgbuilder/test/red/nodes')
22 files changed, 2077 insertions, 0 deletions
diff --git a/dgbuilder/test/red/nodes/Node_spec.js b/dgbuilder/test/red/nodes/Node_spec.js new file mode 100644 index 00000000..6ac54bd2 --- /dev/null +++ b/dgbuilder/test/red/nodes/Node_spec.js @@ -0,0 +1,297 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var sinon = require('sinon'); +var RedNode = require("../../../red/nodes/Node"); +var comms = require('../../../red/comms'); + +describe('Node', function() { + describe('#constructor',function() { + it('is called with an id and a type',function() { + var n = new RedNode({id:'123',type:'abc'}); + n.should.have.property('id','123'); + n.should.have.property('type','abc'); + n.should.not.have.property('name'); + n.wires.should.be.empty; + }); + + it('is called with an id, a type and a name',function() { + var n = new RedNode({id:'123',type:'abc',name:'barney'}); + n.should.have.property('id','123'); + n.should.have.property('type','abc'); + n.should.have.property('name','barney'); + n.wires.should.be.empty; + }); + + it('is called with an id, a type and some wires',function() { + var n = new RedNode({id:'123',type:'abc',wires:['123','456']}); + n.should.have.property('id','123'); + n.should.have.property('type','abc'); + n.should.not.have.property('name'); + n.wires.should.have.length(2); + }); + + }); + + describe('#close', function() { + it('emits close event when closed',function(done) { + var n = new RedNode({id:'123',type:'abc'}); + n.on('close',function() { + done(); + }); + var p = n.close(); + should.not.exist(p); + }); + + it('returns a promise when provided a callback with a done parameter',function(testdone) { + var n = new RedNode({id:'123',type:'abc'}); + n.on('close',function(done) { + setTimeout(function() { + done(); + },200); + }); + var p = n.close(); + should.exist(p); + p.then(function() { + testdone(); + }); + }); + }); + + + describe('#receive', function() { + it('emits input event when called', function(done) { + var n = new RedNode({id:'123',type:'abc'}); + var message = {payload:"hello world"}; + n.on('input',function(msg) { + should.deepEqual(msg,message); + done(); + }); + n.receive(message); + }); + }); + + describe('#send', function() { + + it('emits a single message', function(done) { + var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]}); + var n2 = new RedNode({id:'n2',type:'abc'}); + var message = {payload:"hello world"}; + + n2.on('input',function(msg) { + // msg equals message, but is a new copy + should.deepEqual(msg,message); + should.notStrictEqual(msg,message); + done(); + }); + + n1.send(message); + }); + + it('emits multiple messages on a single output', function(done) { + var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]}); + var n2 = new RedNode({id:'n2',type:'abc'}); + + var messages = [ + {payload:"hello world"}, + {payload:"hello world again"} + ]; + + var rcvdCount = 0; + + n2.on('input',function(msg) { + should.deepEqual(msg,messages[rcvdCount]); + should.notStrictEqual(msg,messages[rcvdCount]); + rcvdCount += 1; + if (rcvdCount == 2) { + done(); + } + }); + n1.send([messages]); + }); + + it('emits messages to multiple outputs', function(done) { + var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2'],['n3'],['n4','n5']]}); + var n2 = new RedNode({id:'n2',type:'abc'}); + var n3 = new RedNode({id:'n3',type:'abc'}); + var n4 = new RedNode({id:'n4',type:'abc'}); + var n5 = new RedNode({id:'n5',type:'abc'}); + + var messages = [ + {payload:"hello world"}, + null, + {payload:"hello world again"} + ]; + + var rcvdCount = 0; + + n2.on('input',function(msg) { + should.deepEqual(msg,messages[0]); + should.notStrictEqual(msg,messages[0]); + rcvdCount += 1; + if (rcvdCount == 3) { + done(); + } + }); + + n3.on('input',function(msg) { + should.fail(null,null,"unexpected message"); + }); + + n4.on('input',function(msg) { + should.deepEqual(msg,messages[2]); + should.notStrictEqual(msg,messages[2]); + rcvdCount += 1; + if (rcvdCount == 3) { + done(); + } + }); + + n5.on('input',function(msg) { + should.deepEqual(msg,messages[2]); + should.notStrictEqual(msg,messages[2]); + rcvdCount += 1; + if (rcvdCount == 3) { + done(); + } + }); + + n1.send(messages); + }); + + it('emits no messages', function(done) { + var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]}); + var n2 = new RedNode({id:'n2',type:'abc'}); + + n2.on('input',function(msg) { + should.fail(null,null,"unexpected message"); + }); + + setTimeout(function() { + done(); + }, 200); + + n1.send(); + }); + + it('emits messages ignoring non-existent nodes', function(done) { + var n1 = new RedNode({id:'n1',type:'abc',wires:[['n9'],['n2']]}); + var n2 = new RedNode({id:'n2',type:'abc'}); + + var messages = [ + {payload:"hello world"}, + {payload:"hello world again"} + ]; + + n2.on('input',function(msg) { + should.deepEqual(msg,messages[1]); + should.notStrictEqual(msg,messages[1]); + done(); + }); + + n1.send(messages); + }); + + it('emits messages without cloning req or res', function(done) { + var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]}); + var n2 = new RedNode({id:'n2',type:'abc'}); + + var req = {}; + var res = {}; + var cloned = {}; + var message = {payload: "foo", cloned: cloned, req: req, res: res}; + + n2.on('input',function(msg) { + should.deepEqual(msg, message); + msg.cloned.should.not.be.exactly(message.cloned); + msg.req.should.be.exactly(message.req); + msg.res.should.be.exactly(message.res); + done(); + }); + + n1.send(message); + }); + + }); + + describe('#log', function() { + it('emits a log message', function(done) { + var n = new RedNode({id:'123',type:'abc'}); + n.on('log',function(obj) { + should.deepEqual({level:"log", id:n.id, + type:n.type, msg:"a log message"}, obj); + done(); + }); + n.log("a log message"); + }); + }); + + describe('#log', function() { + it('emits a log message with a name', function(done) { + var n = new RedNode({id:'123', type:'abc', name:"barney"}); + n.on('log',function(obj) { + should.deepEqual({level:"log", id:n.id, name: "barney", + type:n.type, msg:"a log message"}, obj); + done(); + }); + n.log("a log message"); + }); + }); + + describe('#warn', function() { + it('emits a warning', function(done) { + var n = new RedNode({id:'123',type:'abc'}); + n.on('log',function(obj) { + should.deepEqual({level:"warn", id:n.id, + type:n.type, msg:"a warning"}, obj); + done(); + }); + n.warn("a warning"); + }); + }); + + describe('#error', function() { + it('emits an error message', function(done) { + var n = new RedNode({id:'123',type:'abc'}); + n.on('log',function(obj) { + should.deepEqual({level:"error", id:n.id, + type:n.type, msg:"an error message"}, obj); + done(); + }); + n.error("an error message"); + }); + }); + + describe('#status', function() { + after(function() { + comms.publish.restore(); + }); + it('publishes status', function(done) { + var n = new RedNode({id:'123',type:'abc'}); + var status = {fill:"green",shape:"dot",text:"connected"}; + sinon.stub(comms, 'publish', function(topic, message, retain) { + topic.should.equal('status/123'); + message.should.equal(status); + retain.should.be.true; + done(); + }); + + n.status(status); + }); + }); + +}); diff --git a/dgbuilder/test/red/nodes/credentials_spec.js b/dgbuilder/test/red/nodes/credentials_spec.js new file mode 100644 index 00000000..3d10461f --- /dev/null +++ b/dgbuilder/test/red/nodes/credentials_spec.js @@ -0,0 +1,497 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var sinon = require("sinon"); +var when = require("when"); +var util = require("util"); + +var index = require("../../../red/nodes/index"); +var credentials = require("../../../red/nodes/credentials"); + +describe('Credentials', function() { + + afterEach(function() { + index.clearRegistry(); + }); + + it('loads from storage',function(done) { + + var storage = { + getCredentials: function() { + return when.promise(function(resolve,reject) { + resolve({"a":{"b":1,"c":2}}); + }); + } + }; + + credentials.init(storage); + + credentials.load().then(function() { + + credentials.get("a").should.have.property('b',1); + credentials.get("a").should.have.property('c',2); + + done(); + }); + }); + + + it('saves to storage', function(done) { + var storage = { + getCredentials: function() { + return when.promise(function(resolve,reject) { + resolve({"a":{"b":1,"c":2}}); + }); + }, + saveCredentials: function(creds) { + return when(true); + } + }; + sinon.spy(storage,"saveCredentials"); + credentials.init(storage); + credentials.load().then(function() { + should.not.exist(credentials.get("b")) + credentials.add('b',{"d":3}); + storage.saveCredentials.callCount.should.be.exactly(1); + credentials.get("b").should.have.property('d',3); + storage.saveCredentials.restore(); + done(); + }); + }); + + it('deletes from storage', function(done) { + var storage = { + getCredentials: function() { + return when.promise(function(resolve,reject) { + resolve({"a":{"b":1,"c":2}}); + }); + }, + saveCredentials: function(creds) { + return when(true); + } + }; + sinon.spy(storage,"saveCredentials"); + credentials.init(storage); + credentials.load().then(function() { + should.exist(credentials.get("a")) + credentials.delete('a'); + storage.saveCredentials.callCount.should.be.exactly(1); + should.not.exist(credentials.get("a")); + storage.saveCredentials.restore(); + done(); + }); + + }); + + it('clean up from storage', function(done) { + var storage = { + getCredentials: function() { + return when.promise(function(resolve,reject) { + resolve({"a":{"b":1,"c":2}}); + }); + }, + saveCredentials: function(creds) { + return when(true); + } + }; + sinon.spy(storage,"saveCredentials"); + credentials.init(storage); + credentials.load().then(function() { + should.exist(credentials.get("a")); + credentials.clean(function() { + return false; + }); + storage.saveCredentials.callCount.should.be.exactly(1); + should.not.exist(credentials.get("a")); + storage.saveCredentials.restore(); + done(); + }); + }); + + it('handle error loading from storage', function(done) { + var storage = { + getCredentials: function() { + return when.promise(function(resolve,reject) { + reject("test forcing failure"); + }); + }, + saveCredentials: function(creds) { + return when(true); + } + }; + var logmsg = 'no errors yet'; + sinon.stub(util, 'log', function(msg) { + logmsg = msg; + }); + + credentials.init(storage); + credentials.load().then(function() { + should.equal('[red] Error loading credentials : test forcing failure', logmsg); + util.log.restore(); + done(); + }).otherwise(function(err){ + util.log.restore(); + done(err); + }); + }); + + it('credential type is not registered when extract', function(done) { + var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}]; + var storage = { + getFlows: function() { + var defer = when.defer(); + defer.resolve(testFlows); + return defer.promise; + }, + getCredentials: function() { + return when.promise(function(resolve,reject) { + resolve({"tab1":{"b":1,"c":2}}); + }); + }, + saveFlows: function(conf) { + var defer = when.defer(); + defer.resolve(); + should.deepEqual(testFlows, conf); + return defer.promise; + }, + saveCredentials: function(creds) { + return when(true); + }, + getSettings: function() { + return when({}); + }, + saveSettings: function(s) { + return when(); + } + }; + function TestNode(n) { + index.createNode(this, n); + + this.id = 'tab1'; + this.type = 'test'; + this.name = 'barney'; + var node = this; + + this.on("log", function() { + // do nothing + }); + } + var logmsg = 'nothing logged yet'; + sinon.stub(util, 'log', function(msg) { + logmsg = msg; + }); + var settings = { + available: function() { return false;} + } + index.init(settings, storage); + index.registerType('test', TestNode); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); + credentials.extract(testnode); + should.equal(logmsg, 'Credential Type test is not registered.'); + util.log.restore(); + done(); + }).otherwise(function(err){ + util.log.restore(); + done(err); + }); + }); + + describe('extract and store credential updates in the provided node', function() { + var path = require('path'); + var fs = require('fs-extra'); + var http = require('http'); + var express = require('express'); + var server = require("../../../red/server"); + var localfilesystem = require("../../../red/storage/localfilesystem"); + var app = express(); + var RED = require("../../../red/red.js"); + + var userDir = path.join(__dirname,".testUserHome"); + before(function(done) { + fs.remove(userDir,function(err) { + fs.mkdir(userDir,function() { + sinon.stub(index, 'load', function() { + return when.promise(function(resolve,reject){ + resolve([]); + }); + }); + sinon.stub(localfilesystem, 'getCredentials', function() { + return when.promise(function(resolve,reject) { + resolve({"tab1":{"foo": 2, "pswd":'sticks'}}); + }); + }) ; + RED.init(http.createServer(function(req,res){app(req,res)}), + {userDir: userDir}); + server.start().then(function () { + done(); + }); + }); + }); + }); + + after(function(done) { + fs.remove(userDir,done); + server.stop(); + index.load.restore(); + localfilesystem.getCredentials.restore(); + }); + + function TestNode(n) { + index.createNode(this, n); + var node = this; + this.on("log", function() { + // do nothing + }); + } + + it(': credential updated with good value', function(done) { + index.registerType('test', TestNode, { + credentials: { + foo: {type:"test"} + } + }); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); + credentials.extract(testnode); + should.exist(credentials.get('tab1')); + credentials.get('tab1').should.have.property('foo',2); + + // set credentials to be an updated value and checking this is extracted properly + testnode.credentials = {"foo": 3}; + credentials.extract(testnode); + should.exist(credentials.get('tab1')); + credentials.get('tab1').should.not.have.property('foo',2); + credentials.get('tab1').should.have.property('foo',3); + done(); + }).otherwise(function(err){ + done(err); + }); + }); + + it(': credential updated with empty value', function(done) { + index.registerType('test', TestNode, { + credentials: { + foo: {type:"test"} + } + }); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); + // setting value of "foo" credential to be empty removes foo as a property + testnode.credentials = {"foo": ''}; + credentials.extract(testnode); + should.exist(credentials.get('tab1')); + credentials.get('tab1').should.not.have.property('foo',2); + credentials.get('tab1').should.not.have.property('foo'); + done(); + }).otherwise(function(err){ + done(err); + }); + }); + + it(': undefined credential updated', function(done) { + index.registerType('test', TestNode, { + credentials: { + foo: {type:"test"} + } + }); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); + // setting value of an undefined credential should not change anything + testnode.credentials = {"bar": 4}; + credentials.extract(testnode); + should.exist(credentials.get('tab1')); + credentials.get('tab1').should.have.property('foo',2); + credentials.get('tab1').should.not.have.property('bar'); + done(); + }).otherwise(function(err){ + done(err); + }); + }); + + it(': password credential updated', function(done) { + index.registerType('password', TestNode, { + credentials: { + pswd: {type:"password"} + } + }); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'password',name:'barney'}); + // setting value of password credential should update password + testnode.credentials = {"pswd": 'fiddle'}; + credentials.extract(testnode); + should.exist(credentials.get('tab1')); + credentials.get('tab1').should.have.property('pswd','fiddle'); + credentials.get('tab1').should.not.have.property('pswd','sticks'); + done(); + }).otherwise(function(err){ + done(err); + }); + }); + + it(': password credential not updated', function(done) { + index.registerType('password', TestNode, { + credentials: { + pswd: {type:"password"} + } + }); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'password',name:'barney'}); + // setting value of password credential should update password + testnode.credentials = {"pswd": '__PWRD__'}; + credentials.extract(testnode); + should.exist(credentials.get('tab1')); + credentials.get('tab1').should.have.property('pswd','sticks'); + credentials.get('tab1').should.not.have.property('pswd','__PWRD__'); + done(); + }).otherwise(function(err){ + done(err); + }); + }); + + }) + + describe('registerEndpoint', function() { + var path = require('path'); + var fs = require('fs-extra'); + var http = require('http'); + var express = require('express'); + var request = require('supertest'); + + var server = require("../../../red/server"); + var localfilesystem = require("../../../red/storage/localfilesystem"); + var app = express(); + var RED = require("../../../red/red.js"); + + var userDir = path.join(__dirname,".testUserHome"); + before(function(done) { + fs.remove(userDir,function(err) { + fs.mkdir(userDir,function() { + sinon.stub(index, 'load', function() { + return when.promise(function(resolve,reject){ + resolve([]); + }); + }); + sinon.stub(localfilesystem, 'getCredentials', function() { + return when.promise(function(resolve,reject) { + resolve({"tab1":{"foo": 2, "pswd":'sticks'}}); + }); + }) ; + RED.init(http.createServer(function(req,res){app(req,res)}), + {userDir: userDir}); + server.start().then(function () { + done(); + }); + }); + }); + }); + + after(function(done) { + fs.remove(userDir,done); + server.stop(); + index.load.restore(); + localfilesystem.getCredentials.restore(); + }); + + function TestNode(n) { + index.createNode(this, n); + var node = this; + this.on("log", function() { + // do nothing + }); + } + + it(': valid credential type', function(done) { + index.registerType('test', TestNode, { + credentials: { + foo: {type:"test"} + } + }); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'foo',name:'barney'}); + request(RED.httpAdmin).get('/credentials/test/tab1').expect(200).end(function(err,res) { + if (err) { + done(err); + } + res.body.should.have.property('foo', 2); + done(); + }); + }).otherwise(function(err){ + done(err); + }); + }); + + it(': password credential type', function(done) { + index.registerType('password', TestNode, { + credentials: { + pswd: {type:"password"} + } + }); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'pswd',name:'barney'}); + request(RED.httpAdmin).get('/credentials/password/tab1').expect(200).end(function(err,res) { + if (err) { + done(err); + } + res.body.should.have.property('has_pswd', true); + res.body.should.not.have.property('pswd'); + done(); + }); + }).otherwise(function(err){ + done(err); + }); + }); + + it(': returns 404 for undefined credential type', function(done) { + index.registerType('test', TestNode, { + credentials: { + foo: {type:"test"} + } + }); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'foo',name:'barney'}); + request(RED.httpAdmin).get('/credentials/unknownType/tab1').expect(404).end(done); + }).otherwise(function(err){ + done(err); + }); + }); + + it(': undefined nodeID', function(done) { + index.registerType('test', TestNode, { + credentials: { + foo: {type:"test"} + } + }); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'foo',name:'barney'}); + request(RED.httpAdmin).get('/credentials/test/unknownNode').expect(200).end(function(err,res) { + if (err) { + done(err); + } + var b = res.body; + res.body.should.not.have.property('foo'); + done(); + }); + }).otherwise(function(err){ + done(err); + }); + }); + + }) + +}) + diff --git a/dgbuilder/test/red/nodes/flows_spec.js b/dgbuilder/test/red/nodes/flows_spec.js new file mode 100644 index 00000000..091bf409 --- /dev/null +++ b/dgbuilder/test/red/nodes/flows_spec.js @@ -0,0 +1,134 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var sinon = require("sinon"); +var when = require("when"); +var flows = require("../../../red/nodes/flows"); +var RedNode = require("../../../red/nodes/Node"); +var RED = require("../../../red/nodes"); +var events = require("../../../red/events"); +var typeRegistry = require("../../../red/nodes/registry"); + + +var settings = { + available: function() { return false; } +} + +function loadFlows(testFlows, cb) { + var storage = { + getFlows: function() { + return when.resolve(testFlows); + }, + getCredentials: function() { + return when.resolve({}); + } + }; + RED.init(settings, storage); + flows.load().then(function() { + should.deepEqual(testFlows, flows.getFlows()); + cb(); + }); +} + +describe('flows', function() { + + describe('#add',function() { + it('should be called by node constructor',function(done) { + var n = new RedNode({id:'123',type:'abc'}); + should.deepEqual(n, flows.get("123")); + flows.clear().then(function() { + done(); + }); + }); + }); + + describe('#each',function() { + it('should "visit" all nodes',function(done) { + var nodes = [ + new RedNode({id:'n0'}), + new RedNode({id:'n1'}) + ]; + var count = 0; + flows.each(function(node) { + should.deepEqual(nodes[count], node); + count += 1; + if (count == 2) { + done(); + } + }); + }); + }); + + describe('#load',function() { + it('should load nothing when storage is empty',function(done) { + loadFlows([], done); + }); + + it('should load and start an empty tab flow',function(done) { + loadFlows([{"type":"tab","id":"tab1","label":"Sheet 1"}], function() {}); + events.once('nodes-started', function() { done(); }); + }); + + it('should load and start a registered node type', function(done) { + RED.registerType('debug', function() {}); + var typeRegistryGet = sinon.stub(typeRegistry,"get",function(nt) { + return function() {}; + }); + loadFlows([{"id":"n1","type":"debug"}], function() { }); + events.once('nodes-started', function() { + typeRegistryGet.restore(); + done(); + }); + }); + + it('should load and start when node type is registered', function(done) { + var typeRegistryGet = sinon.stub(typeRegistry,"get"); + typeRegistryGet.onCall(0).returns(null); + typeRegistryGet.returns(function(){}); + + loadFlows([{"id":"n2","type":"inject"}], function() { + events.emit('type-registered','inject'); + }); + events.once('nodes-started', function() { + typeRegistryGet.restore(); + done(); + }); + }); + }); + + describe('#setFlows',function() { + it('should save and start an empty tab flow',function(done) { + var saved = 0; + var testFlows = [{"type":"tab","id":"tab1","label":"Sheet 1"}]; + var storage = { + saveFlows: function(conf) { + var defer = when.defer(); + defer.resolve(); + should.deepEqual(testFlows, conf); + return defer.promise; + }, + saveCredentials: function (creds) { + return when(true); + } + }; + RED.init(settings, storage); + flows.setFlows(testFlows); + events.once('nodes-started', function() { done(); }); + }); + }); + +}); diff --git a/dgbuilder/test/red/nodes/index_spec.js b/dgbuilder/test/red/nodes/index_spec.js new file mode 100644 index 00000000..dcb866e9 --- /dev/null +++ b/dgbuilder/test/red/nodes/index_spec.js @@ -0,0 +1,255 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var fs = require('fs-extra'); +var path = require('path'); +var when = require("when"); +var sinon = require('sinon'); + +var index = require("../../../red/nodes/index"); + +describe("red/nodes/index", function() { + + afterEach(function() { + index.clearRegistry(); + }); + + var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}]; + var storage = { + getFlows: function() { + return when(testFlows); + }, + getCredentials: function() { + return when({"tab1":{"b":1,"c":2}}); + }, + saveFlows: function(conf) { + should.deepEqual(testFlows, conf); + return when(); + }, + saveCredentials: function(creds) { + return when(true); + } + }; + + var settings = { + available: function() { return false } + }; + + function TestNode(n) { + index.createNode(this, n); + var node = this; + this.on("log", function() { + // do nothing + }); + } + + it('nodes are initialised with credentials',function(done) { + + index.init(settings, storage); + index.registerType('test', TestNode); + index.loadFlows().then(function() { + var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); + testnode.credentials.should.have.property('b',1); + testnode.credentials.should.have.property('c',2); + done(); + }).otherwise(function(err) { + done(err); + }); + + }); + + it('flows should be initialised',function(done) { + index.init(settings, storage); + index.loadFlows().then(function() { + should.deepEqual(testFlows, index.getFlows()); + done(); + }).otherwise(function(err) { + done(err); + }); + + }); + + describe("registerType should register credentials definition", function() { + var http = require('http'); + var express = require('express'); + var app = express(); + var server = require("../../../red/server"); + var credentials = require("../../../red/nodes/credentials"); + var localfilesystem = require("../../../red/storage/localfilesystem"); + var RED = require("../../../red/red.js"); + + var userDir = path.join(__dirname,".testUserHome"); + before(function(done) { + fs.remove(userDir,function(err) { + fs.mkdir(userDir,function() { + sinon.stub(index, 'load', function() { + return when.promise(function(resolve,reject){ + resolve([]); + }); + }); + sinon.stub(localfilesystem, 'getCredentials', function() { + return when.promise(function(resolve,reject) { + resolve({"tab1":{"b":1,"c":2}}); + }); + }) ; + RED.init(http.createServer(function(req,res){app(req,res)}), + {userDir: userDir}); + server.start().then(function () { + done(); + }); + }); + }); + }); + + after(function(done) { + fs.remove(userDir,done); + server.stop(); + index.load.restore(); + localfilesystem.getCredentials.restore(); + }); + + it(': definition defined',function(done) { + index.registerType('test', TestNode, { + credentials: { + foo: {type:"test"} + } + }); + var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); + credentials.getDefinition("test").should.have.property('foo'); + done(); + }); + + }); + + describe('allows nodes to be added/remove/enabled/disabled from the registry', function() { + var registry = require("../../../red/nodes/registry"); + var randomNodeInfo = {id:"5678",types:["random"]}; + + before(function() { + sinon.stub(registry,"getNodeInfo",function(id) { + if (id == "test") { + return {id:"1234",types:["test"]}; + } else if (id == "doesnotexist") { + return null; + } else { + return randomNodeInfo; + } + }); + sinon.stub(registry,"removeNode",function(id) { + return randomNodeInfo; + }); + sinon.stub(registry,"disableNode",function(id) { + return randomNodeInfo; + }); + }); + after(function() { + registry.getNodeInfo.restore(); + registry.removeNode.restore(); + registry.disableNode.restore(); + }); + + it(': allows an unused node type to be removed',function(done) { + index.init(settings, storage); + index.registerType('test', TestNode); + index.loadFlows().then(function() { + var info = index.removeNode("5678"); + registry.removeNode.calledOnce.should.be.true; + registry.removeNode.calledWith("5678").should.be.true; + info.should.eql(randomNodeInfo); + done(); + }).otherwise(function(err) { + done(err); + }); + }); + + it(': allows an unused node type to be disabled',function(done) { + index.init(settings, storage); + index.registerType('test', TestNode); + index.loadFlows().then(function() { + var info = index.disableNode("5678"); + registry.disableNode.calledOnce.should.be.true; + registry.disableNode.calledWith("5678").should.be.true; + info.should.eql(randomNodeInfo); + done(); + }).otherwise(function(err) { + done(err); + }); + }); + + it(': prevents removing a node type that is in use',function(done) { + index.init(settings, storage); + index.registerType('test', TestNode); + index.loadFlows().then(function() { + /*jshint immed: false */ + (function() { + index.removeNode("test"); + }).should.throw(); + + done(); + }).otherwise(function(err) { + done(err); + }); + }); + + it(': prevents disabling a node type that is in use',function(done) { + index.init(settings, storage); + index.registerType('test', TestNode); + index.loadFlows().then(function() { + /*jshint immed: false */ + (function() { + index.disabledNode("test"); + }).should.throw(); + + done(); + }).otherwise(function(err) { + done(err); + }); + }); + + it(': prevents removing a node type that is unknown',function(done) { + index.init(settings, storage); + index.registerType('test', TestNode); + index.loadFlows().then(function() { + /*jshint immed: false */ + (function() { + index.removeNode("doesnotexist"); + }).should.throw(); + + done(); + }).otherwise(function(err) { + done(err); + }); + }); + it(': prevents disabling a node type that is unknown',function(done) { + index.init(settings, storage); + index.registerType('test', TestNode); + index.loadFlows().then(function() { + /*jshint immed: false */ + (function() { + index.disableNode("doesnotexist"); + }).should.throw(); + + done(); + }).otherwise(function(err) { + done(err); + }); + }); + + }); + + +}); diff --git a/dgbuilder/test/red/nodes/registry_spec.js b/dgbuilder/test/red/nodes/registry_spec.js new file mode 100644 index 00000000..81c1a2ce --- /dev/null +++ b/dgbuilder/test/red/nodes/registry_spec.js @@ -0,0 +1,808 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var sinon = require("sinon"); +var path = require("path"); +var when = require("when"); + +var RedNodes = require("../../../red/nodes"); +var RedNode = require("../../../red/nodes/Node"); +var typeRegistry = require("../../../red/nodes/registry"); +var events = require("../../../red/events"); + +afterEach(function() { + typeRegistry.clear(); +}); + +describe('NodeRegistry', function() { + + var resourcesDir = __dirname+ path.sep + "resources" + path.sep; + + function stubSettings(s,available) { + s.available = function() {return available;} + s.set = function(s,v) { return when.resolve()}, + s.get = function(s) { return null;} + return s + } + var settings = stubSettings({},false); + var settingsWithStorage = stubSettings({},true); + + it('automatically registers new nodes',function() { + var testNode = RedNodes.getNode('123'); + should.not.exist(n); + var n = new RedNode({id:'123',type:'abc'}); + + var newNode = RedNodes.getNode('123'); + + should.strictEqual(n,newNode); + }); + + it('handles nodes that export a function', function(done) { + typeRegistry.init(settings); + typeRegistry.load(resourcesDir + "TestNode1",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNode1.js"); + list[0].should.have.property("types",["test-node-1"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + + var nodeConstructor = typeRegistry.get("test-node-1"); + nodeConstructor.should.be.type("function"); + + done(); + }).catch(function(e) { + done(e); + }); + + }); + + + it('handles nodes that export a function returning a resolving promise', function(done) { + typeRegistry.init(settings); + typeRegistry.load(resourcesDir + "TestNode2",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNode2.js"); + list[0].should.have.property("types",["test-node-2"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + var nodeConstructor = typeRegistry.get("test-node-2"); + nodeConstructor.should.be.type("function"); + + done(); + }).catch(function(e) { + done(e); + }); + + }); + + it('handles nodes that export a function returning a rejecting promise', function(done) { + typeRegistry.init(settings); + typeRegistry.load(resourcesDir + "TestNode3",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNode3.js"); + list[0].should.have.property("types",["test-node-3"]); + list[0].should.have.property("enabled",true); + list[0].should.have.property("err","fail"); + + var nodeConstructor = typeRegistry.get("test-node-3"); + (nodeConstructor === null).should.be.true; + + done(); + }).catch(function(e) { + done(e); + }); + + }); + + it('handles files containing multiple nodes', function(done) { + typeRegistry.init(settings); + typeRegistry.load(resourcesDir + "MultipleNodes1",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("id"); + list[0].should.have.property("name","MultipleNodes1.js"); + list[0].should.have.property("types",["test-node-multiple-1a","test-node-multiple-1b"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + + var nodeConstructor = typeRegistry.get("test-node-multiple-1a"); + nodeConstructor.should.be.type("function"); + + nodeConstructor = typeRegistry.get("test-node-multiple-1b"); + nodeConstructor.should.be.type("function"); + + done(); + }).catch(function(e) { + done(e); + }); + }); + + it('handles nested directories', function(done) { + typeRegistry.init(settings); + typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("id"); + list[0].should.have.property("name","NestedNode.js"); + list[0].should.have.property("types",["nested-node-1"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + done(); + }).catch(function(e) { + done(e); + }); + }); + + it('emits type-registered and node-icon-dir events', function(done) { + var eventEmitSpy = sinon.spy(events,"emit"); + typeRegistry.init(settings); + typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("name","NestedNode.js"); + list[0].should.have.property("types",["nested-node-1"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + + eventEmitSpy.callCount.should.equal(2); + + eventEmitSpy.firstCall.args[0].should.be.equal("node-icon-dir"); + eventEmitSpy.firstCall.args[1].should.be.equal( + resourcesDir + "NestedDirectoryNode" + path.sep + "NestedNode" + path.sep + "icons"); + + eventEmitSpy.secondCall.args[0].should.be.equal("type-registered"); + eventEmitSpy.secondCall.args[1].should.be.equal("nested-node-1"); + + done(); + }).catch(function(e) { + done(e); + }).finally(function() { + eventEmitSpy.restore(); + }); + }); + + it('rejects a duplicate node type registration', function(done) { + + typeRegistry.init(stubSettings({ + nodesDir:[resourcesDir + "TestNode1",resourcesDir + "DuplicateTestNode"] + },false)); + typeRegistry.load("wontexist",true).then(function() { + var list = typeRegistry.getNodeList(); + + list.should.be.an.Array.and.have.lengthOf(2); + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNode1.js"); + list[0].should.have.property("types",["test-node-1"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + + list[1].should.have.property("id"); + list[1].id.should.not.equal(list[0].id); + + list[1].should.have.property("name","TestNode1.js"); + list[1].should.have.property("types",["test-node-1"]); + list[1].should.have.property("enabled",true); + list[1].should.have.property("err"); + /already registered/.test(list[1].err).should.be.true; + + var nodeConstructor = typeRegistry.get("test-node-1"); + // Verify the duplicate node hasn't replaced the original one + nodeConstructor.name.should.be.equal("TestNode"); + + done(); + }).catch(function(e) { + done(e); + }); + }); + + it('handles nodesDir as a string', function(done) { + + typeRegistry.init(stubSettings({ + nodesDir :resourcesDir + "TestNode1" + },false)); + typeRegistry.load("wontexist",true).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("types",["test-node-1"]); + done(); + }).catch(function(e) { + done("Loading of non-existing nodesDir should succeed"); + }); + + }); + + it('handles invalid nodesDir',function(done) { + + typeRegistry.init(stubSettings({ + nodesDir : "wontexist" + },false)); + typeRegistry.load("wontexist",true).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.be.empty; + done(); + }).catch(function(e) { + done("Loading of non-existing nodesDir should succeed"); + }); + }); + + it('returns nothing for an unregistered type config', function() { + typeRegistry.init(settings); + typeRegistry.load("wontexist",true).then(function(){ + var config = typeRegistry.getNodeConfig("imaginary-shark"); + (config === null).should.be.true; + }).catch(function(e) { + done(e); + }); + }); + + it('excludes node files listed in nodesExcludes',function(done) { + typeRegistry.init(stubSettings({ + nodesExcludes: [ "TestNode1.js" ], + nodesDir:[resourcesDir + "TestNode1",resourcesDir + "TestNode2"] + },false)); + typeRegistry.load("wontexist",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("types",["test-node-2"]); + done(); + }).catch(function(e) { + done(e); + }); + }); + + it('returns the node configurations', function(done) { + typeRegistry.init(stubSettings({ + nodesDir:[resourcesDir + "TestNode1",resourcesDir + "TestNode2"] + },false)); + typeRegistry.load("wontexist",true).then(function() { + var list = typeRegistry.getNodeList(); + + var nodeConfigs = typeRegistry.getNodeConfigs(); + + // TODO: this is brittle... + nodeConfigs.should.equal("<script type=\"text/x-red\" data-template-name=\"test-node-1\"></script>\n<script type=\"text/x-red\" data-help-name=\"test-node-1\"></script>\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-1',{});</script>\n<style></style>\n<p>this should be filtered out</p>\n<script type=\"text/x-red\" data-template-name=\"test-node-2\"></script>\n<script type=\"text/x-red\" data-help-name=\"test-node-2\"></script>\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-2',{});</script>\n<style></style>\n"); + + var nodeId = list[0].id; + var nodeConfig = typeRegistry.getNodeConfig(nodeId); + nodeConfig.should.equal("<script type=\"text/x-red\" data-template-name=\"test-node-1\"></script>\n<script type=\"text/x-red\" data-help-name=\"test-node-1\"></script>\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-1',{});</script>\n<style></style>\n<p>this should be filtered out</p>\n"); + done(); + }).catch(function(e) { + done(e); + }); + }); + + it('stores the node list', function(done) { + var settings = { + nodesDir:[resourcesDir + "TestNode1",resourcesDir + "TestNode2",resourcesDir + "TestNode3"], + available: function() { return true; }, + set: function(s,v) {return when.resolve();}, + get: function(s) { return null;} + } + var settingsSave = sinon.spy(settings,"set"); + typeRegistry.init(settings); + typeRegistry.load("wontexist",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.Array.and.have.length(3); + + settingsSave.callCount.should.equal(1); + settingsSave.firstCall.args[0].should.be.equal("nodes"); + var savedList = settingsSave.firstCall.args[1]; + + savedList[list[0].id].name == list[0].name; + savedList[list[1].id].name == list[1].name; + savedList[list[2].id].name == list[2].name; + + savedList[list[0].id].should.not.have.property("err"); + savedList[list[1].id].should.not.have.property("err"); + savedList[list[2].id].should.not.have.property("err"); + + done(); + }).catch(function(e) { + done(e); + }).finally(function() { + settingsSave.restore(); + }); + + }); + + it('allows nodes to be added by filename', function(done) { + var settings = { + available: function() { return true; }, + set: function(s,v) {return when.resolve();}, + get: function(s) { return null;} + } + typeRegistry.init(settings); + typeRegistry.load("wontexist",true).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.be.empty; + + typeRegistry.addNode(resourcesDir + "TestNode1/TestNode1.js").then(function(node) { + list = typeRegistry.getNodeList(); + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNode1.js"); + list[0].should.have.property("types",["test-node-1"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + + node.should.be.an.Array.and.have.lengthOf(1); + node.should.eql(list); + + done(); + }).catch(function(e) { + done(e); + }); + + }).catch(function(e) { + done(e); + }); + }); + + it('fails to add non-existent filename', function(done) { + typeRegistry.init(settingsWithStorage); + typeRegistry.load("wontexist",true).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.be.empty; + typeRegistry.addNode(resourcesDir + "DoesNotExist/DoesNotExist.js").then(function(nodes) { + nodes.should.be.an.Array.and.have.lengthOf(1); + nodes[0].should.have.property("id"); + nodes[0].should.have.property("types",[]); + nodes[0].should.have.property("err"); + done(); + }).otherwise(function(e) { + done(e); + }); + + }).catch(function(e) { + done(e); + }); + }); + + it('returns node info by type or id', function(done) { + typeRegistry.init(settings); + typeRegistry.load(resourcesDir + "TestNode1",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + + var id = list[0].id; + var type = list[0].types[0]; + + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNode1.js"); + list[0].should.have.property("types",["test-node-1"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + + var info = typeRegistry.getNodeInfo(id); + list[0].should.eql(info); + + var info2 = typeRegistry.getNodeInfo(type); + list[0].should.eql(info2); + + done(); + }).catch(function(e) { + done(e); + }); + + }); + + + it('rejects adding duplicate nodes', function(done) { + typeRegistry.init(settingsWithStorage); + typeRegistry.load(resourcesDir + "TestNode1",true).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + + typeRegistry.addNode({file:resourcesDir + "TestNode1" + path.sep + "TestNode1.js"}).then(function(node) { + done(new Error("duplicate node loaded")); + }).otherwise(function(e) { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + done(); + }); + + }).catch(function(e) { + done(e); + }); + }); + + it('removes nodes from the registry', function(done) { + typeRegistry.init(settingsWithStorage); + typeRegistry.load(resourcesDir + "TestNode1",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNode1.js"); + list[0].should.have.property("types",["test-node-1"]); + list[0].should.have.property("enabled",true); + list[0].should.have.property("loaded",true); + + typeRegistry.getNodeConfigs().length.should.be.greaterThan(0); + + var info = typeRegistry.removeNode(list[0].id); + + info.should.have.property("id",list[0].id); + info.should.have.property("enabled",false); + info.should.have.property("loaded",false); + + typeRegistry.getNodeList().should.be.an.Array.and.be.empty; + typeRegistry.getNodeConfigs().length.should.equal(0); + + var nodeConstructor = typeRegistry.get("test-node-1"); + (typeof nodeConstructor).should.be.equal("undefined"); + + + done(); + }).catch(function(e) { + done(e); + }); + }); + + it('rejects removing unknown nodes from the registry', function(done) { + typeRegistry.init(settings); + typeRegistry.load("wontexist",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.be.empty; + + + /*jshint immed: false */ + (function() { + typeRegistry.removeNode("1234"); + }).should.throw(); + + done(); + }).catch(function(e) { + done(e); + }); + }); + + it('scans the node_modules path for node files', function(done) { + var fs = require("fs"); + var path = require("path"); + + var eventEmitSpy = sinon.spy(events,"emit"); + var pathJoin = (function() { + var _join = path.join; + return sinon.stub(path,"join",function() { + if (arguments.length == 3 && arguments[2] == "package.json") { + return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1],arguments[2]); + } + if (arguments.length == 2 && arguments[1] == "TestNodeModule") { + return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1]); + } + return _join.apply(this,arguments); + }); + })(); + + var readdirSync = (function() { + var originalReaddirSync = fs.readdirSync; + var callCount = 0; + return sinon.stub(fs,"readdirSync",function(dir) { + var result = []; + if (callCount == 1) { + result = originalReaddirSync(resourcesDir + "TestNodeModule" + path.sep + "node_modules"); + } + callCount++; + return result; + }); + })(); + + typeRegistry.init(settings); + typeRegistry.load("wontexist",false).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(2); + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNodeModule:TestNodeMod1"); + list[0].should.have.property("types",["test-node-mod-1"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + + list[1].should.have.property("id"); + list[1].should.have.property("name","TestNodeModule:TestNodeMod2"); + list[1].should.have.property("types",["test-node-mod-2"]); + list[1].should.have.property("enabled",true); + list[1].should.have.property("err"); + + + eventEmitSpy.callCount.should.equal(2); + + eventEmitSpy.firstCall.args[0].should.be.equal("node-icon-dir"); + eventEmitSpy.firstCall.args[1].should.be.equal( + resourcesDir + "TestNodeModule" + path.sep+ "node_modules" + path.sep + "TestNodeModule" + path.sep + "icons"); + + eventEmitSpy.secondCall.args[0].should.be.equal("type-registered"); + eventEmitSpy.secondCall.args[1].should.be.equal("test-node-mod-1"); + + done(); + }).catch(function(e) { + done(e); + }).finally(function() { + readdirSync.restore(); + pathJoin.restore(); + eventEmitSpy.restore(); + }); + }); + + it('allows nodes to be added by module name', function(done) { + var fs = require("fs"); + var path = require("path"); + + var pathJoin = (function() { + var _join = path.join; + return sinon.stub(path,"join",function() { + if (arguments.length == 3 && arguments[2] == "package.json") { + return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1],arguments[2]); + } + if (arguments.length == 2 && arguments[1] == "TestNodeModule") { + return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1]); + } + return _join.apply(this,arguments); + }); + })(); + + var readdirSync = (function() { + var originalReaddirSync = fs.readdirSync; + var callCount = 0; + return sinon.stub(fs,"readdirSync",function(dir) { + var result = []; + if (callCount == 1) { + result = originalReaddirSync(resourcesDir + "TestNodeModule" + path.sep + "node_modules"); + } + callCount++; + return result; + }); + })(); + typeRegistry.init(settingsWithStorage); + typeRegistry.load("wontexist",true).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.be.empty; + + typeRegistry.addModule("TestNodeModule").then(function(node) { + list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(2); + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNodeModule:TestNodeMod1"); + list[0].should.have.property("types",["test-node-mod-1"]); + list[0].should.have.property("enabled",true); + list[0].should.not.have.property("err"); + + list[1].should.have.property("id"); + list[1].should.have.property("name","TestNodeModule:TestNodeMod2"); + list[1].should.have.property("types",["test-node-mod-2"]); + list[1].should.have.property("enabled",true); + list[1].should.have.property("err"); + + node.should.eql(list); + + done(); + }).catch(function(e) { + done(e); + }); + + }).catch(function(e) { + done(e); + }).finally(function() { + readdirSync.restore(); + pathJoin.restore(); + }); + }); + + + it('rejects adding duplicate node modules', function(done) { + var fs = require("fs"); + var path = require("path"); + + var pathJoin = (function() { + var _join = path.join; + return sinon.stub(path,"join",function() { + if (arguments.length == 3 && arguments[2] == "package.json") { + return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1],arguments[2]); + } + if (arguments.length == 2 && arguments[1] == "TestNodeModule") { + return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1]); + } + return _join.apply(this,arguments); + }); + })(); + + var readdirSync = (function() { + var originalReaddirSync = fs.readdirSync; + var callCount = 0; + return sinon.stub(fs,"readdirSync",function(dir) { + var result = []; + if (callCount == 1) { + result = originalReaddirSync(resourcesDir + "TestNodeModule" + path.sep + "node_modules"); + } + callCount++; + return result; + }); + })(); + + typeRegistry.init(settingsWithStorage); + typeRegistry.load('wontexist',false).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(2); + typeRegistry.addModule("TestNodeModule").then(function(node) { + done(new Error("addModule resolved")); + }).otherwise(function(err) { + done(); + }); + }).catch(function(e) { + done(e); + }).finally(function() { + readdirSync.restore(); + pathJoin.restore(); + }); + }); + + + it('fails to add non-existent module name', function(done) { + typeRegistry.init(settingsWithStorage); + typeRegistry.load("wontexist",true).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.be.empty; + + typeRegistry.addModule("DoesNotExistModule").then(function(node) { + done(new Error("ENOENT not thrown")); + }).otherwise(function(e) { + e.code.should.eql("MODULE_NOT_FOUND"); + done(); + }); + + }).catch(function(e) { + done(e); + }); + }); + + it('removes nodes from the registry by module', function(done) { + var fs = require("fs"); + var path = require("path"); + + var pathJoin = (function() { + var _join = path.join; + return sinon.stub(path,"join",function() { + if (arguments.length == 3 && arguments[2] == "package.json") { + return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1],arguments[2]); + } + if (arguments.length == 2 && arguments[1] == "TestNodeModule") { + return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1]); + } + return _join.apply(this,arguments); + }); + })(); + + var readdirSync = (function() { + var originalReaddirSync = fs.readdirSync; + var callCount = 0; + return sinon.stub(fs,"readdirSync",function(dir) { + var result = []; + if (callCount == 1) { + result = originalReaddirSync(resourcesDir + "TestNodeModule" + path.sep + "node_modules"); + } + callCount++; + return result; + }); + })(); + + typeRegistry.init(settingsWithStorage); + typeRegistry.load('wontexist',false).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(2); + var res = typeRegistry.removeModule("TestNodeModule"); + + res.should.be.an.Array.and.have.lengthOf(2); + res[0].should.have.a.property("id",list[0].id); + res[1].should.have.a.property("id",list[1].id); + + list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.be.empty; + + done(); + }).catch(function(e) { + done(e); + }).finally(function() { + readdirSync.restore(); + pathJoin.restore(); + }); + + }); + + it('fails to remove non-existent module name', function(done) { + typeRegistry.init(settings); + typeRegistry.load("wontexist",true).then(function(){ + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.be.empty; + + /*jshint immed: false */ + (function() { + typeRegistry.removeModule("DoesNotExistModule"); + }).should.throw(); + + done(); + + }).catch(function(e) { + done(e); + }); + }); + + + it('allows nodes to be enabled and disabled', function(done) { + typeRegistry.init(settingsWithStorage); + typeRegistry.load(resourcesDir+path.sep+"TestNode1",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.have.lengthOf(1); + list[0].should.have.property("id"); + list[0].should.have.property("name","TestNode1.js"); + list[0].should.have.property("enabled",true); + + var nodeConfig = typeRegistry.getNodeConfigs(); + nodeConfig.length.should.be.greaterThan(0); + + var info = typeRegistry.disableNode(list[0].id); + info.should.have.property("id",list[0].id); + info.should.have.property("enabled",false); + + var list2 = typeRegistry.getNodeList(); + list2.should.be.an.Array.and.have.lengthOf(1); + list2[0].should.have.property("enabled",false); + + typeRegistry.getNodeConfigs().length.should.equal(0); + + var info2 = typeRegistry.enableNode(list[0].id); + info2.should.have.property("id",list[0].id); + info2.should.have.property("enabled",true); + + var list3 = typeRegistry.getNodeList(); + list3.should.be.an.Array.and.have.lengthOf(1); + list3[0].should.have.property("enabled",true); + + var nodeConfig2 = typeRegistry.getNodeConfigs(); + nodeConfig2.should.eql(nodeConfig); + + done(); + }).catch(function(e) { + done(e); + }); + }); + + it('fails to enable/disable non-existent nodes', function(done) { + typeRegistry.init(settings); + typeRegistry.load("wontexist",true).then(function() { + var list = typeRegistry.getNodeList(); + list.should.be.an.Array.and.be.empty; + + /*jshint immed: false */ + (function() { + typeRegistry.disableNode("123"); + }).should.throw(); + + /*jshint immed: false */ + (function() { + typeRegistry.enableNode("123"); + }).should.throw(); + + done(); + }).catch(function(e) { + done(e); + }); + }); +}); diff --git a/dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.html b/dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.html new file mode 100644 index 00000000..b637ede2 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.html @@ -0,0 +1,3 @@ +<script type="text/x-red" data-template-name="test-node-1"></script> +<script type="text/x-red" data-help-name="test-node-1"></script> +<script type="text/javascript">RED.nodes.registerType('test-node-1',{});</script> diff --git a/dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.js b/dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.js new file mode 100644 index 00000000..e8121416 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.js @@ -0,0 +1,5 @@ +// A test node that exports a function +module.exports = function(RED) { + function DuplicateTestNode(n) {} + RED.nodes.registerType("test-node-1",DuplicateTestNode); +} diff --git a/dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.html b/dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.html new file mode 100644 index 00000000..5359644e --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.html @@ -0,0 +1,6 @@ +<script type="text/x-red" data-template-name="test-node-multiple-1a"></script> +<script type="text/x-red" data-help-name="test-node-multiple-1a"></script> +<script type="text/javascript">RED.nodes.registerType('test-node-multiple-1a',{});</script> +<script type="text/x-red" data-template-name="test-node-multiple-1b"></script> +<script type="text/x-red" data-help-name="test-node-multiple-1b"></script> +<script type="text/javascript">RED.nodes.registerType('test-node-multiple-1b',{});</script> diff --git a/dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.js b/dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.js new file mode 100644 index 00000000..55747c0b --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.js @@ -0,0 +1,7 @@ +// A test node that exports a function +module.exports = function(RED) { + function TestNode1(n) {} + RED.nodes.registerType("test-node-multiple-1a",TestNode1); + function TestNode2(n) {} + RED.nodes.registerType("test-node-multiple-1b",TestNode2); +} diff --git a/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.html b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.html new file mode 100644 index 00000000..abc823e8 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.html @@ -0,0 +1,4 @@ +<script type="text/x-red" data-template-name="nested-node-1"></script> +<script type="text/x-red" data-help-name="nested-node-1"></script> +<script type="text/javascript">RED.nodes.registerType('nested-node-1',{});</script> +<style></style> diff --git a/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.js b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.js new file mode 100644 index 00000000..cd3148a5 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.js @@ -0,0 +1,5 @@ +// A test node that exports a function +module.exports = function(RED) { + function TestNode(n) {} + RED.nodes.registerType("nested-node-1",TestNode); +} diff --git a/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/icons/file.txt b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/icons/file.txt new file mode 100644 index 00000000..59a29af1 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/icons/file.txt @@ -0,0 +1,3 @@ +This file exists just to ensure the 'icons' directory is in the repository. +TODO: a future test needs to ensure the right icon files are loaded - this + directory can be used for that diff --git a/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.html b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.html new file mode 100644 index 00000000..ac9235d2 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.html @@ -0,0 +1,4 @@ +<script type="text/x-red" data-template-name="should-not-load-1"></script> +<script type="text/x-red" data-help-name="should-not-load-1"></script> +<script type="text/javascript">RED.nodes.registerType('should-not-load-1',{});</script> +<style></style> diff --git a/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.js b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.js new file mode 100644 index 00000000..8af249b1 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.js @@ -0,0 +1,5 @@ +// A test node that exports a function +module.exports = function(RED) { + function TestNode(n) {} + RED.nodes.registerType("should-not-load-1",TestNode); +} diff --git a/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.html b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.html new file mode 100644 index 00000000..4212fd58 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.html @@ -0,0 +1,4 @@ +<script type="text/x-red" data-template-name="should-not-load-3"></script> +<script type="text/x-red" data-help-name="should-not-load-3"></script> +<script type="text/javascript">RED.nodes.registerType('should-not-load-3',{});</script> +<style></style> diff --git a/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.js b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.js new file mode 100644 index 00000000..5856adad --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.js @@ -0,0 +1,5 @@ +// A test node that exports a function +module.exports = function(RED) { + function TestNode(n) {} + RED.nodes.registerType("should-not-load-3",TestNode); +} diff --git a/dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.html b/dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.html new file mode 100644 index 00000000..97dbf171 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.html @@ -0,0 +1,5 @@ +<script type="text/x-red" data-template-name="test-node-1"></script> +<script type="text/x-red" data-help-name="test-node-1"></script> +<script type="text/javascript">RED.nodes.registerType('test-node-1',{});</script> +<style></style> +<p>this should be filtered out</p> diff --git a/dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.js b/dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.js new file mode 100644 index 00000000..bfa3b65b --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.js @@ -0,0 +1,5 @@ +// A test node that exports a function +module.exports = function(RED) { + function TestNode(n) {} + RED.nodes.registerType("test-node-1",TestNode); +} diff --git a/dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.html b/dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.html new file mode 100644 index 00000000..66b65909 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.html @@ -0,0 +1,4 @@ +<script type="text/x-red" data-template-name="test-node-2"></script> +<script type="text/x-red" data-help-name="test-node-2"></script> +<script type="text/javascript">RED.nodes.registerType('test-node-2',{});</script> +<style></style> diff --git a/dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.js b/dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.js new file mode 100644 index 00000000..faf61a8f --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.js @@ -0,0 +1,10 @@ +// A test node that exports a function which returns a resolving promise + +var when = require("when"); +module.exports = function(RED) { + return when.promise(function(resolve,reject) { + function TestNode(n) {} + RED.nodes.registerType("test-node-2",TestNode); + resolve(); + }); +} diff --git a/dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.html b/dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.html new file mode 100644 index 00000000..9a0f6f7e --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.html @@ -0,0 +1,3 @@ +<script type="text/x-red" data-template-name="test-node-3"></script> +<script type="text/x-red" data-help-name="test-node-3"></script> +<script type="text/javascript">RED.nodes.registerType('test-node-3',{});</script> diff --git a/dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.js b/dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.js new file mode 100644 index 00000000..756dc139 --- /dev/null +++ b/dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.js @@ -0,0 +1,8 @@ +// A test node that exports a function which returns a rejecting promise + +var when = require("when"); +module.exports = function(RED) { + return when.promise(function(resolve,reject) { + reject("fail"); + }); +} |