summaryrefslogtreecommitdiffstats
path: root/dgbuilder/test/red
diff options
context:
space:
mode:
Diffstat (limited to 'dgbuilder/test/red')
-rw-r--r--dgbuilder/test/red/cli/lib/config_spec.js53
-rw-r--r--dgbuilder/test/red/cli/lib/request_spec.js46
-rw-r--r--dgbuilder/test/red/cli/nr-cli_spec.js15
-rw-r--r--dgbuilder/test/red/comms_spec.js189
-rw-r--r--dgbuilder/test/red/events_spec.js22
-rw-r--r--dgbuilder/test/red/library_spec.js237
-rw-r--r--dgbuilder/test/red/log_spec.js22
-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
-rw-r--r--dgbuilder/test/red/red_spec.js22
-rw-r--r--dgbuilder/test/red/server_spec.js22
-rw-r--r--dgbuilder/test/red/settings_spec.js114
-rw-r--r--dgbuilder/test/red/storage/index_spec.js128
-rw-r--r--dgbuilder/test/red/storage/localfilesystem_spec.js367
-rw-r--r--dgbuilder/test/red/ui_spec.js177
-rw-r--r--dgbuilder/test/red/util_spec.js70
36 files changed, 3561 insertions, 0 deletions
diff --git a/dgbuilder/test/red/cli/lib/config_spec.js b/dgbuilder/test/red/cli/lib/config_spec.js
new file mode 100644
index 00000000..68e960af
--- /dev/null
+++ b/dgbuilder/test/red/cli/lib/config_spec.js
@@ -0,0 +1,53 @@
+/**
+ * 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 fs = require("fs");
+
+var config = require("../../../../red/cli/lib/config");
+
+describe("cli config", function() {
+ afterEach(function() {
+ config.unload();
+ });
+ it('loads preferences when target referenced', sinon.test(function() {
+ this.stub(fs,"readFileSync",function() {
+ return '{"target":"http://example.com:1880"}'
+ });
+ config.target.should.eql("http://example.com:1880");
+ }));
+ it('provide default value for target', sinon.test(function() {
+ this.stub(fs,"readFileSync",function() {
+ return '{}'
+ });
+ config.target.should.eql("http://localhost:1880");
+ }));
+ it('saves preferences when target set', sinon.test(function() {
+ this.stub(fs,"readFileSync",function() {
+ return '{"target":"http://another.example.com:1880"}'
+ });
+ this.stub(fs,"writeFileSync",function() {});
+
+ config.target.should.eql("http://another.example.com:1880");
+ config.target = "http://final.example.com:1880";
+
+ fs.readFileSync.calledOnce.should.be.true;
+ fs.writeFileSync.calledOnce.should.be.true;
+
+ }));
+
+}); \ No newline at end of file
diff --git a/dgbuilder/test/red/cli/lib/request_spec.js b/dgbuilder/test/red/cli/lib/request_spec.js
new file mode 100644
index 00000000..7d2b5ac7
--- /dev/null
+++ b/dgbuilder/test/red/cli/lib/request_spec.js
@@ -0,0 +1,46 @@
+/**
+ * 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 fs = require("fs");
+var request = require("request");
+
+var apiRequest = require("../../../../red/cli/lib/request");
+var config = require("../../../../red/cli/lib/config");
+
+describe("cli request", function() {
+ var sandbox = sinon.sandbox.create();
+ before(function() {
+ sandbox.stub(fs,"readFileSync",function() {
+ return '{"target":"http://example.com:1880"}'
+ });
+ });
+ after(function() {
+ sandbox.restore();
+ });
+
+ it('returns the json response to a get', sinon.test(function(done) {
+ this.stub(request, 'get').yields(null, {statusCode:200}, JSON.stringify({a: "b"}));
+
+ apiRequest("/foo",{}).then(function(res) {
+ res.should.eql({a:"b"});
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }));
+}); \ No newline at end of file
diff --git a/dgbuilder/test/red/cli/nr-cli_spec.js b/dgbuilder/test/red/cli/nr-cli_spec.js
new file mode 100644
index 00000000..59a5c641
--- /dev/null
+++ b/dgbuilder/test/red/cli/nr-cli_spec.js
@@ -0,0 +1,15 @@
+/**
+ * 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.
+ **/ \ No newline at end of file
diff --git a/dgbuilder/test/red/comms_spec.js b/dgbuilder/test/red/comms_spec.js
new file mode 100644
index 00000000..dce4d83a
--- /dev/null
+++ b/dgbuilder/test/red/comms_spec.js
@@ -0,0 +1,189 @@
+/**
+ * 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 http = require('http');
+var express = require('express');
+var app = express();
+var WebSocket = require('ws');
+
+var comms = require("../../red/comms.js");
+var address = '127.0.0.1';
+var listenPort = 0; // use ephemeral port
+
+describe("comms", function() {
+ describe("with default keepalive", function() {
+ var server;
+ var url;
+ var port;
+ before(function(done) {
+ server = http.createServer(function(req,res){app(req,res)});
+ comms.init(server, {});
+ server.listen(listenPort, address);
+ server.on('listening', function() {
+ port = server.address().port;
+ url = 'http://' + address + ':' + port + '/comms';
+ comms.start();
+ done();
+ });
+ });
+
+ after(function() {
+ comms.stop();
+ });
+
+ it('accepts connection', function(done) {
+ var ws = new WebSocket(url);
+ ws.on('open', function() {
+ ws.close();
+ done();
+ });
+ });
+
+ it('publishes message after subscription', function(done) {
+ var ws = new WebSocket(url);
+ ws.on('open', function() {
+ ws.send('{"subscribe":"topic1"}');
+ comms.publish('topic1', 'foo');
+ });
+ ws.on('message', function(msg) {
+ msg.should.equal('{"topic":"topic1","data":"foo"}');
+ ws.close();
+ done();
+ });
+ });
+
+ it('publishes retained message for subscription', function(done) {
+ comms.publish('topic2', 'bar', true);
+ var ws = new WebSocket(url);
+ ws.on('open', function() {
+ ws.send('{"subscribe":"topic2"}');
+ });
+ ws.on('message', function(msg) {
+ msg.should.equal('{"topic":"topic2","data":"bar"}');
+ ws.close();
+ done();
+ });
+ });
+
+ it('retained message is deleted by non-retained message', function(done) {
+ comms.publish('topic3', 'retained', true);
+ comms.publish('topic3', 'non-retained');
+ var ws = new WebSocket(url);
+ ws.on('open', function() {
+ ws.send('{"subscribe":"topic3"}');
+ comms.publish('topic3', 'new');
+ });
+ ws.on('message', function(msg) {
+ msg.should.equal('{"topic":"topic3","data":"new"}');
+ ws.close();
+ done();
+ });
+ });
+
+ it('malformed messages are ignored',function(done) {
+ var ws = new WebSocket(url);
+ ws.on('open', function() {
+ ws.send('not json');
+ ws.send('[]');
+ ws.send('{"subscribe":"topic3"}');
+ comms.publish('topic3', 'correct');
+ });
+ ws.on('message', function(msg) {
+ msg.should.equal('{"topic":"topic3","data":"correct"}');
+ ws.close();
+ done();
+ });
+ });
+
+ // The following test currently fails due to minimum viable
+ // implementation. More test should be written to test topic
+ // matching once this one is passing
+
+ if (0) {
+ it('receives message on correct topic', function(done) {
+ var ws = new WebSocket(url);
+ ws.on('open', function() {
+ ws.send('{"subscribe":"topic4"}');
+ comms.publish('topic5', 'foo');
+ comms.publish('topic4', 'bar');
+ });
+ ws.on('message', function(msg) {
+ msg.should.equal('{"topic":"topic4","data":"bar"}');
+ ws.close();
+ done();
+ });
+ });
+ }
+ });
+
+ describe("keep alives", function() {
+ var server;
+ var url;
+ var port;
+ before(function(done) {
+ server = http.createServer(function(req,res){app(req,res)});
+ comms.init(server, {webSocketKeepAliveTime: 100});
+ server.listen(listenPort, address);
+ server.on('listening', function() {
+ port = server.address().port;
+ url = 'http://' + address + ':' + port + '/comms';
+ comms.start();
+ done();
+ });
+ });
+ after(function() {
+ comms.stop();
+ });
+ it('are sent', function(done) {
+ var ws = new WebSocket(url);
+ var count = 0;
+ ws.on('message', function(data) {
+ var msg = JSON.parse(data);
+ msg.should.have.property('topic','hb');
+ msg.should.have.property('data').be.a.Number;
+ count++;
+ if (count == 3) {
+ ws.close();
+ done();
+ }
+ });
+ });
+ it('are not sent if other messages are sent', function(done) {
+ var ws = new WebSocket(url);
+ var count = 0;
+ var interval;
+ ws.on('open', function() {
+ ws.send('{"subscribe":"foo"}');
+ interval = setInterval(function() {
+ comms.publish('foo', 'bar');
+ }, 50);
+ });
+ ws.on('message', function(data) {
+ var msg = JSON.parse(data);
+ msg.should.have.property('topic', 'foo');
+ msg.should.have.property('data', 'bar');
+ count++;
+ if (count == 5) {
+ clearInterval(interval);
+ ws.close();
+ done();
+ }
+ });
+ });
+ });
+
+});
diff --git a/dgbuilder/test/red/events_spec.js b/dgbuilder/test/red/events_spec.js
new file mode 100644
index 00000000..24759262
--- /dev/null
+++ b/dgbuilder/test/red/events_spec.js
@@ -0,0 +1,22 @@
+/**
+ * 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");
+
+describe("red/events", function() {
+ it('can be required without errors', function() {
+ require("../../red/events");
+ });
+});
diff --git a/dgbuilder/test/red/library_spec.js b/dgbuilder/test/red/library_spec.js
new file mode 100644
index 00000000..52255287
--- /dev/null
+++ b/dgbuilder/test/red/library_spec.js
@@ -0,0 +1,237 @@
+/**
+ * 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 request = require('supertest');
+var http = require('http');
+var express = require('express');
+
+var fs = require('fs-extra');
+var path = require('path');
+var when = require('when');
+
+var app = express();
+var RED = require("../../red/red.js");
+var server = require("../../red/server.js");
+var nodes = require("../../red/nodes");
+
+describe("library", function() {
+ var userDir = path.join(__dirname,".testUserHome");
+ before(function(done) {
+ fs.remove(userDir,function(err) {
+ fs.mkdir(userDir,function() {
+ sinon.stub(nodes, 'load', function() {
+ return when.promise(function(resolve,reject){
+ resolve([]);
+ });
+ });
+ 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();
+ nodes.load.restore();
+ });
+
+ afterEach(function(done) {
+ fs.remove(userDir,function(err) {
+ fs.mkdir(userDir,done);
+ });
+ });
+
+ describe("flows", function() {
+ it('returns empty result', function(done) {
+ request(RED.httpAdmin)
+ .get('/library/flows')
+ .expect(200)
+ .end(function(err,res) {
+ if (err) {
+ throw err;
+ }
+ res.body.should.not.have.property('f');
+ done();
+ });
+ });
+
+ it('returns 404 for non-existent entry', function(done) {
+ request(RED.httpAdmin)
+ .get('/library/flows/foo')
+ .expect(404)
+ .end(done);
+ });
+
+ it('can store and retrieve item', function(done) {
+ var flow = '[]';
+ request(RED.httpAdmin)
+ .post('/library/flows/foo')
+ .set('Content-Type', 'text/plain')
+ .send(flow)
+ .expect(204).end(function (err, res) {
+ if (err) {
+ throw err;
+ }
+ request(RED.httpAdmin)
+ .get('/library/flows/foo')
+ .expect(200)
+ .end(function(err,res) {
+ if (err) {
+ throw err;
+ }
+ res.text.should.equal(flow);
+ done();
+ });
+ });
+ });
+
+ it('lists a stored item', function(done) {
+ request(RED.httpAdmin)
+ .post('/library/flows/bar')
+ .expect(204)
+ .end(function () {
+ request(RED.httpAdmin)
+ .get('/library/flows')
+ .expect(200)
+ .end(function(err,res) {
+ if (err) {
+ throw err;
+ }
+ res.body.should.have.property('f');
+ should.deepEqual(res.body.f,['bar']);
+ done();
+ });
+ });
+ });
+
+ it('returns 403 for malicious access attempt', function(done) {
+ // without the userDir override the malicious url would be
+ // http://127.0.0.1:1880/library/flows/../../package to
+ // obtain package.json from the node-red root.
+ request(RED.httpAdmin)
+ .get('/library/flows/../../../../../package')
+ .expect(403)
+ .end(done);
+ });
+
+ it('returns 403 for malicious access attempt', function(done) {
+ // without the userDir override the malicious url would be
+ // http://127.0.0.1:1880/library/flows/../../package to
+ // obtain package.json from the node-red root.
+ request(RED.httpAdmin)
+ .post('/library/flows/../../../../../package')
+ .expect(403)
+ .end(done);
+ });
+
+ });
+
+ describe("type", function() {
+ before(function() {
+ RED.library.register('test');
+ });
+
+ it('returns empty result', function(done) {
+ request(RED.httpAdmin)
+ .get('/library/test')
+ .expect(200)
+ .end(function(err,res) {
+ if (err) {
+ throw err;
+ }
+ res.body.should.not.have.property('f');
+ done();
+ });
+ });
+
+ it('returns 404 for non-existent entry', function(done) {
+ request(RED.httpAdmin)
+ .get('/library/test/foo')
+ .expect(404)
+ .end(done);
+ });
+
+ it('can store and retrieve item', function(done) {
+ var flow = '[]';
+ request(RED.httpAdmin)
+ .post('/library/test/foo')
+ .set('Content-Type', 'text/plain')
+ .send(flow)
+ .expect(204).end(function (err, res) {
+ if (err) {
+ throw err;
+ }
+ request(RED.httpAdmin)
+ .get('/library/test/foo')
+ .expect(200)
+ .end(function(err,res) {
+ if (err) {
+ throw err;
+ }
+ res.text.should.equal(flow);
+ done();
+ });
+ });
+ });
+
+ it('lists a stored item', function(done) {
+ request(RED.httpAdmin)
+ .post('/library/test/bar')
+ .expect(204)
+ .end(function () {
+ request(RED.httpAdmin)
+ .get('/library/test')
+ .expect(200)
+ .end(function(err,res) {
+ if (err) {
+ throw err;
+ }
+ should.deepEqual(res.body,[{ fn: 'bar'}]);
+ done();
+ });
+ });
+ });
+
+
+ it('returns 403 for malicious access attempt', function(done) {
+ request(RED.httpAdmin)
+ .get('/library/test/../../../../../../../../../../etc/passwd')
+ .expect(403)
+ .end(done);
+ });
+
+ it('returns 403 for malicious access attempt', function(done) {
+ request(RED.httpAdmin)
+ .get('/library/test/..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd')
+ .expect(403)
+ .end(done);
+ });
+
+ it('returns 403 for malicious access attempt', function(done) {
+ request(RED.httpAdmin)
+ .post('/library/test/../../../../../../../../../../etc/passwd')
+ .set('Content-Type', 'text/plain')
+ .send('root:x:0:0:root:/root:/usr/bin/tclsh')
+ .expect(403)
+ .end(done);
+ });
+
+ });
+});
diff --git a/dgbuilder/test/red/log_spec.js b/dgbuilder/test/red/log_spec.js
new file mode 100644
index 00000000..0fb0aafd
--- /dev/null
+++ b/dgbuilder/test/red/log_spec.js
@@ -0,0 +1,22 @@
+/**
+ * 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");
+
+describe("red/log", function() {
+ it('can be required without errors', function() {
+ require("../../red/log");
+ });
+});
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");
+ });
+}
diff --git a/dgbuilder/test/red/red_spec.js b/dgbuilder/test/red/red_spec.js
new file mode 100644
index 00000000..f61fd55b
--- /dev/null
+++ b/dgbuilder/test/red/red_spec.js
@@ -0,0 +1,22 @@
+/**
+ * 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");
+
+describe("red/red", function() {
+ it('can be required without errors', function() {
+ require("../../red/red");
+ });
+});
diff --git a/dgbuilder/test/red/server_spec.js b/dgbuilder/test/red/server_spec.js
new file mode 100644
index 00000000..b20249ca
--- /dev/null
+++ b/dgbuilder/test/red/server_spec.js
@@ -0,0 +1,22 @@
+/**
+ * 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");
+
+describe("red/server", function() {
+ it('can be required without errors', function() {
+ require("../../red/server");
+ });
+});
diff --git a/dgbuilder/test/red/settings_spec.js b/dgbuilder/test/red/settings_spec.js
new file mode 100644
index 00000000..fb4cbade
--- /dev/null
+++ b/dgbuilder/test/red/settings_spec.js
@@ -0,0 +1,114 @@
+/**
+ * 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 when = require("when");
+
+var settings = require("../../red/settings");
+
+
+describe("red/settings", function() {
+
+ afterEach(function() {
+ settings.reset();
+ });
+
+ it('wraps the user settings as read-only properties', function() {
+ var userSettings = {
+ a: 123,
+ b: "test",
+ c: [1,2,3]
+ }
+ settings.init(userSettings);
+
+ settings.available().should.be.false;
+
+ settings.a.should.equal(123);
+ settings.b.should.equal("test");
+ settings.c.should.be.an.Array.with.lengthOf(3);
+
+ settings.get("a").should.equal(123);
+ settings.get("b").should.equal("test");
+ settings.get("c").should.be.an.Array.with.lengthOf(3);
+
+ /*jshint immed: false */
+ (function() {
+ settings.a = 456;
+ }).should.throw();
+
+ settings.c.push(5);
+ settings.c.should.be.an.Array.with.lengthOf(4);
+
+ /*jshint immed: false */
+ (function() {
+ settings.set("a",456);
+ }).should.throw();
+
+ /*jshint immed: false */
+ (function() {
+ settings.set("a",456);
+ }).should.throw();
+
+ /*jshint immed: false */
+ (function() {
+ settings.get("unknown");
+ }).should.throw();
+
+ /*jshint immed: false */
+ (function() {
+ settings.set("unknown",456);
+ }).should.throw();
+
+ });
+
+ it('loads global settings from storage', function(done) {
+ var userSettings = {
+ a: 123,
+ b: "test",
+ c: [1,2,3]
+ }
+ var savedSettings = null;
+ var storage = {
+ getSettings: function() {
+ return when.resolve({globalA:789});
+ },
+ saveSettings: function(settings) {
+ savedSettings = settings;
+ return when.resolve();
+ }
+ }
+ settings.init(userSettings);
+
+ settings.available().should.be.false;
+
+ /*jshint immed: false */
+ (function() {
+ settings.get("unknown");
+ }).should.throw();
+
+ settings.load(storage).then(function() {
+ settings.available().should.be.true;
+ settings.get("globalA").should.equal(789);
+ settings.set("globalA","abc").then(function() {
+ savedSettings.globalA.should.equal("abc");
+ done();
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+
+
+ });
+});
diff --git a/dgbuilder/test/red/storage/index_spec.js b/dgbuilder/test/red/storage/index_spec.js
new file mode 100644
index 00000000..4b60ba83
--- /dev/null
+++ b/dgbuilder/test/red/storage/index_spec.js
@@ -0,0 +1,128 @@
+/**
+ * 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");
+
+describe("red/storage/index", function() {
+
+ it('rejects the promise when settings suggest loading a bad module', function(done) {
+
+ var wrongModule = {
+ storageModule : "thisaintloading"
+ };
+
+ var storage = require("../../../red/storage/index");
+ storage.init(wrongModule).then( function() {
+ var one = 1;
+ var zero = 0;
+ try {
+ zero.should.equal(one, "The initialization promise should never get resolved");
+ } catch(err) {
+ done(err);
+ }
+ }).catch(function(e) {
+ done(); //successfully rejected promise
+ });
+ });
+
+ it('non-string storage module', function(done) {
+ var initSetsMeToTrue = false;
+
+ var moduleWithBooleanSettingInit = {
+ init : function() {
+ initSetsMeToTrue = true;
+ }
+ };
+
+ var setsBooleanModule = {
+ storageModule : moduleWithBooleanSettingInit
+ };
+
+ var storage = require("../../../red/storage/index");
+ storage.init(setsBooleanModule);
+ initSetsMeToTrue.should.be.true;
+ done();
+ });
+
+ it('respects storage interface', function(done) {
+ var calledFlagGetFlows = false;
+ var calledFlagGetCredentials = false;
+ var calledFlagGetAllFlows = false;
+ var calledInit = false;
+
+ var interfaceCheckerModule = {
+ init : function (settings) {
+ settings.should.be.an.Object;
+ calledInit = true;
+ },
+ getFlows : function() {
+ calledFlagGetFlows = true;
+ },
+ saveFlows : function (flows) {
+ flows.should.be.true;
+ },
+ getCredentials : function() {
+ calledFlagGetCredentials = true;
+ },
+ saveCredentials : function(credentials) {
+ credentials.should.be.true;
+ },
+ getAllFlows : function() {
+ calledFlagGetAllFlows = true;
+ },
+ getFlow : function(fn) {
+ fn.should.equal("name");
+ },
+ saveFlow : function(fn, data) {
+ fn.should.equal("name");
+ data.should.be.true;
+ },
+ getLibraryEntry : function(type, path) {
+ type.should.be.true;
+ path.should.equal("name");
+ },
+ saveLibraryEntry : function(type, path, meta, body) {
+ type.should.be.true;
+ path.should.equal("name");
+ meta.should.be.true;
+ body.should.be.true;
+ }
+ };
+
+ var moduleToLoad = {
+ storageModule : interfaceCheckerModule
+ };
+ var storage = require("../../../red/storage/index");
+
+ storage.init(moduleToLoad);
+ storage.getFlows();
+ storage.saveFlows(true);
+ storage.getCredentials();
+ storage.saveCredentials(true);
+ storage.getAllFlows();
+ storage.getFlow("name");
+ storage.saveFlow("name", true);
+ storage.getLibraryEntry(true, "name");
+ storage.saveLibraryEntry(true, "name", true, true);
+
+ calledInit.should.be.true;
+ calledFlagGetFlows.should.be.true;
+ calledFlagGetCredentials.should.be.true;
+ calledFlagGetAllFlows.should.be.true;
+
+ done();
+ });
+
+});
diff --git a/dgbuilder/test/red/storage/localfilesystem_spec.js b/dgbuilder/test/red/storage/localfilesystem_spec.js
new file mode 100644
index 00000000..a1311709
--- /dev/null
+++ b/dgbuilder/test/red/storage/localfilesystem_spec.js
@@ -0,0 +1,367 @@
+/**
+ * Copyright 2013, 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 localfilesystem = require("../../../red/storage/localfilesystem");
+
+describe('LocalFileSystem', function() {
+ var userDir = path.join(__dirname,".testUserHome");
+ var testFlow = [{"type":"tab","id":"d8be2a6d.2741d8","label":"Sheet 1"}];
+ beforeEach(function(done) {
+ fs.remove(userDir,function(err) {
+ fs.mkdir(userDir,done);
+ });
+ });
+ afterEach(function(done) {
+ fs.remove(userDir,done);
+ });
+
+ it('should initialise the user directory',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ fs.existsSync(path.join(userDir,"lib")).should.be.true;
+ fs.existsSync(path.join(userDir,"lib",'flows')).should.be.true;
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should handle missing flow file',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ var flowFile = 'flows_'+require('os').hostname()+'.json';
+ var flowFilePath = path.join(userDir,flowFile);
+ fs.existsSync(flowFilePath).should.be.false;
+ localfilesystem.getFlows().then(function(flows) {
+ flows.should.eql([]);
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should save flows to the default file',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ var flowFile = 'flows_'+require('os').hostname()+'.json';
+ var flowFilePath = path.join(userDir,flowFile);
+ var flowFileBackupPath = path.join(userDir,"flows.backup");
+ fs.existsSync(flowFilePath).should.be.false;
+ fs.existsSync(flowFileBackupPath).should.be.false;
+ localfilesystem.saveFlows(testFlow).then(function() {
+ fs.existsSync(flowFilePath).should.be.true;
+ fs.existsSync(flowFileBackupPath).should.be.false;
+ localfilesystem.getFlows().then(function(flows) {
+ flows.should.eql(testFlow);
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should save flows to the specified file',function(done) {
+ var defaultFlowFile = 'flows_'+require('os').hostname()+'.json';
+ var defaultFlowFilePath = path.join(userDir,defaultFlowFile);
+ var flowFile = 'test.json';
+ var flowFilePath = path.join(userDir,flowFile);
+
+ localfilesystem.init({userDir:userDir, flowFile:flowFilePath}).then(function() {
+ fs.existsSync(defaultFlowFilePath).should.be.false;
+ fs.existsSync(flowFilePath).should.be.false;
+
+ localfilesystem.saveFlows(testFlow).then(function() {
+ fs.existsSync(defaultFlowFilePath).should.be.false;
+ fs.existsSync(flowFilePath).should.be.true;
+ localfilesystem.getFlows().then(function(flows) {
+ flows.should.eql(testFlow);
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should backup the flows file', function(done) {
+ var defaultFlowFile = 'flows_'+require('os').hostname()+'.json';
+ var defaultFlowFilePath = path.join(userDir,defaultFlowFile);
+ var flowFile = 'test.json';
+ var flowFilePath = path.join(userDir,flowFile);
+ var flowFileBackupPath = path.join(userDir,"flows.backup");
+
+ localfilesystem.init({userDir:userDir, flowFile:flowFilePath}).then(function() {
+ fs.existsSync(defaultFlowFilePath).should.be.false;
+ fs.existsSync(flowFilePath).should.be.false;
+ fs.existsSync(flowFileBackupPath).should.be.false;
+
+ localfilesystem.saveFlows(testFlow).then(function() {
+ fs.existsSync(flowFileBackupPath).should.be.false;
+ fs.existsSync(defaultFlowFilePath).should.be.false;
+ fs.existsSync(flowFilePath).should.be.true;
+ var content = fs.readFileSync(flowFilePath,'utf8');
+ var testFlow2 = [{"type":"tab","id":"bc5672ad.2741d8","label":"Sheet 2"}];
+
+ localfilesystem.saveFlows(testFlow2).then(function() {
+ fs.existsSync(flowFileBackupPath).should.be.true;
+ fs.existsSync(defaultFlowFilePath).should.be.false;
+ fs.existsSync(flowFilePath).should.be.true;
+ var backupContent = fs.readFileSync(flowFileBackupPath,'utf8');
+ content.should.equal(backupContent);
+ var content2 = fs.readFileSync(flowFilePath,'utf8');
+ content2.should.not.equal(backupContent);
+ done();
+
+ }).otherwise(function(err) {
+ done(err);
+ });
+
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+
+
+ });
+
+ it('should handle missing credentials', function(done) {
+ var flowFile = 'test.json';
+ var flowFilePath = path.join(userDir,flowFile);
+ var credFile = path.join(userDir,"test_cred.json");
+ localfilesystem.init({userDir:userDir, flowFile:flowFilePath}).then(function() {
+ fs.existsSync(credFile).should.be.false;
+
+ localfilesystem.getCredentials().then(function(creds) {
+ creds.should.eql({});
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should handle credentials', function(done) {
+ var flowFile = 'test.json';
+ var flowFilePath = path.join(userDir,flowFile);
+ var credFile = path.join(userDir,"test_cred.json");
+
+ localfilesystem.init({userDir:userDir, flowFile:flowFilePath}).then(function() {
+
+ fs.existsSync(credFile).should.be.false;
+
+ var credentials = {"abc":{"type":"creds"}};
+
+ localfilesystem.saveCredentials(credentials).then(function() {
+ fs.existsSync(credFile).should.be.true;
+ localfilesystem.getCredentials().then(function(creds) {
+ creds.should.eql(credentials);
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should return an empty list of library flows',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ localfilesystem.getAllFlows().then(function(flows) {
+ flows.should.eql({});
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should return a valid list of library flows',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ var flowLib = path.join(userDir,"lib","flows");
+ fs.closeSync(fs.openSync(path.join(flowLib,"A.json"),"w"));
+ fs.closeSync(fs.openSync(path.join(flowLib,"B.json"),"w"));
+ fs.mkdirSync(path.join(flowLib,"C"));
+ fs.closeSync(fs.openSync(path.join(flowLib,"C","D.json"),"w"));
+ var testFlowsList = {"d":{"C":{"f":["D"]}},"f":["A","B"]};
+
+ localfilesystem.getAllFlows().then(function(flows) {
+ flows.should.eql(testFlowsList);
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should fail a non-existent flow', function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ localfilesystem.getFlow("a/b/c.json").then(function(flow) {
+ should.fail(flow,"No flow","Flow found");
+ }).otherwise(function(err) {
+ // err should be null, so this will pass
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should return a flow',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ var testflowString = JSON.stringify(testFlow);
+ localfilesystem.saveFlow("a/b/c/d.json",testflowString).then(function() {
+ localfilesystem.getFlow("a/b/c/d.json").then(function(flow) {
+ flow.should.eql(testflowString);
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should return an empty list of library objects',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ localfilesystem.getLibraryEntry('object','').then(function(flows) {
+ flows.should.eql({});
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should return an error for a non-existent library object',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ localfilesystem.getLibraryEntry('object','A/B').then(function(flows) {
+ should.fail(null,null,"non-existent flow");
+ }).otherwise(function(err) {
+ should.exist(err);
+ done();
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ function createObjectLibrary() {
+ var objLib = path.join(userDir,"lib","object");
+ fs.mkdirSync(objLib);
+ fs.mkdirSync(path.join(objLib,"A"));
+ fs.mkdirSync(path.join(objLib,"B"));
+ fs.mkdirSync(path.join(objLib,"B","C"));
+ fs.writeFileSync(path.join(objLib,"file1.js"),"// abc: def\n// not a metaline \n\n Hi",'utf8');
+ fs.writeFileSync(path.join(objLib,"B","file2.js"),"// ghi: jkl\n// not a metaline \n\n Hi",'utf8');
+ }
+
+ it('should return a directory listing of library objects',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ createObjectLibrary();
+
+ localfilesystem.getLibraryEntry('object','').then(function(flows) {
+ flows.should.eql([ 'A', 'B', { abc: 'def', fn: 'file1.js' } ]);
+ localfilesystem.getLibraryEntry('object','B').then(function(flows) {
+ flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' } ]);
+ localfilesystem.getLibraryEntry('object','B/C').then(function(flows) {
+ flows.should.eql([]);
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should return a library object',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ createObjectLibrary();
+ localfilesystem.getLibraryEntry('object','B/file2.js').then(function(body) {
+ body.should.eql("// not a metaline \n\n Hi");
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+ it('should return a newly saved library object',function(done) {
+ localfilesystem.init({userDir:userDir}).then(function() {
+ createObjectLibrary();
+ localfilesystem.getLibraryEntry('object','B').then(function(flows) {
+ flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' } ]);
+ localfilesystem.saveLibraryEntry('object','B/D/file3.js',{mno:'pqr'},"// another non meta line\n\n Hi There").then(function() {
+ localfilesystem.getLibraryEntry('object','B/D').then(function(flows) {
+ flows.should.eql([ { mno: 'pqr', fn: 'file3.js' } ]);
+ localfilesystem.getLibraryEntry('object','B/D/file3.js').then(function(body) {
+ body.should.eql("// another non meta line\n\n Hi There");
+ done();
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ }).otherwise(function(err) {
+ done(err);
+ });
+ });
+
+});
diff --git a/dgbuilder/test/red/ui_spec.js b/dgbuilder/test/red/ui_spec.js
new file mode 100644
index 00000000..b9de7bff
--- /dev/null
+++ b/dgbuilder/test/red/ui_spec.js
@@ -0,0 +1,177 @@
+/**
+ * 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 request = require("supertest");
+var express = require("express");
+var redUI = require("../../red/ui");
+
+
+describe("red/ui icon handler", function() {
+ it('returns the default icon when getting an unknown icon', function(done) {
+ var app = express();
+ redUI({},app);
+ request(app)
+ .get("/icons/youwonthaveme.png")
+ .expect('Content-Type', /image\/png/)
+ .expect(200)
+ .end(function(err, res){
+ if (err){
+ return done(err);
+ }
+ done();
+ });
+ });
+
+ it('returns an icon from disk', function(done) {
+ var app = express();
+ redUI({},app);
+ request(app)
+ .get("/icons/arduino.png")
+ .expect('Content-Type', /image\/png/)
+ .expect(200)
+ .end(function(err, res){
+ if (err){
+ return done(err);
+ }
+ done();
+ });
+ });
+});
+
+describe("icon cache handler", function() {
+ var fs = require('fs-extra');
+ var path = require('path');
+ var events = require("../../red/events");
+
+ var tempDir = path.join(__dirname,".tmp/");
+ var cachedFakePNG = tempDir + "cacheMe.png";
+
+
+ beforeEach(function(done) {
+ fs.remove(tempDir,function(err) {
+ fs.mkdirSync(tempDir);
+ fs.writeFileSync(cachedFakePNG, "Hello PNG\n");
+ done();
+ });
+ });
+ afterEach(function(done) {
+ fs.exists(cachedFakePNG, function(exists) {
+ if(exists) {
+ fs.unlinkSync(cachedFakePNG);
+ }
+ fs.remove(tempDir,done);
+ })
+ });
+
+ /*
+ * This test case test that:
+ * 1) any directory can be added to the path lookup (such as /tmp) by
+ * calling the right event
+ * 2) that a file we know exists gets cached so that the lookup/verification
+ * of actual existence doesn't occur again when a subsequent request comes in
+ *
+ * The second point verifies that the cache works. If the cache wouldn't work
+ * the default PNG would be served
+ */
+ it('returns an icon using icon cache', function(done) {
+ var app = express();
+ redUI({},app);
+ events.emit("node-icon-dir", tempDir);
+ request(app)
+ .get("/icons/cacheMe.png")
+ .expect('Content-Type', /image\/png/)
+ .expect(200)
+ .end(function(err, res){
+ if (err){
+ return done(err);
+ }
+ fs.unlink(cachedFakePNG, function(err) {
+ if(err) {
+ return done(err);
+ }
+ request(app)
+ .get("/icons/cacheMe.png")
+ .expect('Content-Type', /text\/html/)
+ .expect(404)
+ .end(function(err, res){
+ if (err){
+ return done(err);
+ }
+ done();
+ });
+ });
+ });
+ });
+});
+
+describe("red/ui settings handler", function() {
+ it('returns the provided settings', function(done) {
+ var settings = {
+ httpNodeRoot: "testHttpNodeRoot",
+ version: "testVersion",
+ };
+ var app = express();
+ redUI(settings,app);
+ request(app)
+ .get("/settings")
+ .expect('Content-Type', /application\/json/)
+ .expect(200, "{\n \"httpNodeRoot\": \"testHttpNodeRoot\",\n \"version\": \"testVersion\"\n}")
+ .end(function(err, res){
+ if (err){
+ return done(err);
+ }
+ done();
+ });
+
+ });
+});
+
+describe("red/ui root handler", function() {
+ it('server up the main page', function(done) {
+ var app = express();
+ redUI({},app);
+
+ request(app)
+ .get("/")
+ .expect('Content-Type', /text\/html/)
+ .expect(200)
+ .end(function(err, res){
+ if (err){
+ return done(err);
+ }
+ done();
+ });
+
+ });
+
+ it('redirects to path ending with /', function(done) {
+ var rapp = express();
+ redUI({},rapp);
+
+ var app = express().use('/root', rapp);
+
+ request(app)
+ .get("/root")
+ .expect('Content-Type', /text\/plain/)
+ .expect(302)
+ .end(function(err, res){
+ if (err){
+ return done(err);
+ }
+ done();
+ });
+
+ });
+});
diff --git a/dgbuilder/test/red/util_spec.js b/dgbuilder/test/red/util_spec.js
new file mode 100644
index 00000000..5200ef12
--- /dev/null
+++ b/dgbuilder/test/red/util_spec.js
@@ -0,0 +1,70 @@
+/**
+ * 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 util = require("../../red/util");
+
+describe("red/util", function() {
+ describe('ensureString', function() {
+ it('strings are preserved', function() {
+ util.ensureString('string').should.equal('string');
+ });
+ it('Buffer is converted', function() {
+ var s = util.ensureString(new Buffer('foo'));
+ s.should.equal('foo');
+ (typeof s).should.equal('string');
+ });
+ it('Object is converted to JSON', function() {
+ var s = util.ensureString({foo: "bar"});
+ (typeof s).should.equal('string');
+ should.deepEqual(JSON.parse(s), {foo:"bar"});
+ });
+ it('stringifies other things', function() {
+ var s = util.ensureString(123);
+ (typeof s).should.equal('string');
+ s.should.equal('123');
+ });
+ });
+
+ describe('ensureBuffer', function() {
+ it('Buffers are preserved', function() {
+ var b = new Buffer('');
+ util.ensureBuffer(b).should.equal(b);
+ });
+ it('string is converted', function() {
+ var b = util.ensureBuffer('foo');
+ var expected = new Buffer('foo');
+ for (var i = 0; i < expected.length; i++) {
+ b[i].should.equal(expected[i]);
+ }
+ Buffer.isBuffer(b).should.equal(true);
+ });
+ it('Object is converted to JSON', function() {
+ var obj = {foo: "bar"}
+ var b = util.ensureBuffer(obj);
+ Buffer.isBuffer(b).should.equal(true);
+ should.deepEqual(JSON.parse(b), obj);
+ });
+ it('stringifies other things', function() {
+ var b = util.ensureBuffer(123);
+ Buffer.isBuffer(b).should.equal(true);
+ var expected = new Buffer('123');
+ for (var i = 0; i < expected.length; i++) {
+ b[i].should.equal(expected[i]);
+ }
+ });
+ });
+});
+