summaryrefslogtreecommitdiffstats
path: root/dgbuilder/test/red/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'dgbuilder/test/red/nodes')
-rw-r--r--dgbuilder/test/red/nodes/Node_spec.js297
-rw-r--r--dgbuilder/test/red/nodes/credentials_spec.js497
-rw-r--r--dgbuilder/test/red/nodes/flows_spec.js134
-rw-r--r--dgbuilder/test/red/nodes/index_spec.js255
-rw-r--r--dgbuilder/test/red/nodes/registry_spec.js808
-rw-r--r--dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.html3
-rw-r--r--dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.js5
-rw-r--r--dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.html6
-rw-r--r--dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.js7
-rw-r--r--dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.html4
-rw-r--r--dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.js5
-rw-r--r--dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/icons/file.txt3
-rw-r--r--dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.html4
-rw-r--r--dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.js5
-rw-r--r--dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.html4
-rw-r--r--dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.js5
-rw-r--r--dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.html5
-rw-r--r--dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.js5
-rw-r--r--dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.html4
-rw-r--r--dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.js10
-rw-r--r--dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.html3
-rw-r--r--dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.js8
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");
+ });
+}