From d1569975bb18f4359fac18aa98f55b69c248a3ad Mon Sep 17 00:00:00 2001 From: "Chinthakayala, Sheshashailavas (sc2914)" Date: Mon, 28 Aug 2017 05:25:46 -0900 Subject: [CCSDK-28] populated the seed code for dgbuilder updated the code to point to the new package name for sli Change-Id: I3b5a1d05dc5193664fd4a667afdcd0b2354010a4 Issue-ID:{CCSDK-28} Signed-off-by: Chinthakayala, Sheshashailavas (sc2914) Signed-off-by: Chinthakayala, Sheshashailavas (sc2914) --- dgbuilder/test/red/cli/lib/config_spec.js | 53 ++ dgbuilder/test/red/cli/lib/request_spec.js | 46 ++ dgbuilder/test/red/cli/nr-cli_spec.js | 15 + dgbuilder/test/red/comms_spec.js | 189 +++++ dgbuilder/test/red/events_spec.js | 22 + dgbuilder/test/red/library_spec.js | 237 ++++++ dgbuilder/test/red/log_spec.js | 22 + dgbuilder/test/red/nodes/Node_spec.js | 297 ++++++++ dgbuilder/test/red/nodes/credentials_spec.js | 497 +++++++++++++ dgbuilder/test/red/nodes/flows_spec.js | 134 ++++ dgbuilder/test/red/nodes/index_spec.js | 255 +++++++ dgbuilder/test/red/nodes/registry_spec.js | 808 +++++++++++++++++++++ .../resources/DuplicateTestNode/TestNode1.html | 3 + .../nodes/resources/DuplicateTestNode/TestNode1.js | 5 + .../resources/MultipleNodes1/MultipleNodes1.html | 6 + .../resources/MultipleNodes1/MultipleNodes1.js | 7 + .../NestedDirectoryNode/NestedNode/NestedNode.html | 4 + .../NestedDirectoryNode/NestedNode/NestedNode.js | 5 + .../NestedDirectoryNode/NestedNode/icons/file.txt | 3 + .../NestedNode/lib/ShouldNotLoad.html | 4 + .../NestedNode/lib/ShouldNotLoad.js | 5 + .../NestedNode/test/ShouldNotLoad.html | 4 + .../NestedNode/test/ShouldNotLoad.js | 5 + .../red/nodes/resources/TestNode1/TestNode1.html | 5 + .../red/nodes/resources/TestNode1/TestNode1.js | 5 + .../red/nodes/resources/TestNode2/TestNode2.html | 4 + .../red/nodes/resources/TestNode2/TestNode2.js | 10 + .../red/nodes/resources/TestNode3/TestNode3.html | 3 + .../red/nodes/resources/TestNode3/TestNode3.js | 8 + dgbuilder/test/red/red_spec.js | 22 + dgbuilder/test/red/server_spec.js | 22 + dgbuilder/test/red/settings_spec.js | 114 +++ dgbuilder/test/red/storage/index_spec.js | 128 ++++ dgbuilder/test/red/storage/localfilesystem_spec.js | 367 ++++++++++ dgbuilder/test/red/ui_spec.js | 177 +++++ dgbuilder/test/red/util_spec.js | 70 ++ 36 files changed, 3561 insertions(+) create mode 100644 dgbuilder/test/red/cli/lib/config_spec.js create mode 100644 dgbuilder/test/red/cli/lib/request_spec.js create mode 100644 dgbuilder/test/red/cli/nr-cli_spec.js create mode 100644 dgbuilder/test/red/comms_spec.js create mode 100644 dgbuilder/test/red/events_spec.js create mode 100644 dgbuilder/test/red/library_spec.js create mode 100644 dgbuilder/test/red/log_spec.js create mode 100644 dgbuilder/test/red/nodes/Node_spec.js create mode 100644 dgbuilder/test/red/nodes/credentials_spec.js create mode 100644 dgbuilder/test/red/nodes/flows_spec.js create mode 100644 dgbuilder/test/red/nodes/index_spec.js create mode 100644 dgbuilder/test/red/nodes/registry_spec.js create mode 100644 dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.html create mode 100644 dgbuilder/test/red/nodes/resources/DuplicateTestNode/TestNode1.js create mode 100644 dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.html create mode 100644 dgbuilder/test/red/nodes/resources/MultipleNodes1/MultipleNodes1.js create mode 100644 dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.html create mode 100644 dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/NestedNode.js create mode 100644 dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/icons/file.txt create mode 100644 dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.html create mode 100644 dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/lib/ShouldNotLoad.js create mode 100644 dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.html create mode 100644 dgbuilder/test/red/nodes/resources/NestedDirectoryNode/NestedNode/test/ShouldNotLoad.js create mode 100644 dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.html create mode 100644 dgbuilder/test/red/nodes/resources/TestNode1/TestNode1.js create mode 100644 dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.html create mode 100644 dgbuilder/test/red/nodes/resources/TestNode2/TestNode2.js create mode 100644 dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.html create mode 100644 dgbuilder/test/red/nodes/resources/TestNode3/TestNode3.js create mode 100644 dgbuilder/test/red/red_spec.js create mode 100644 dgbuilder/test/red/server_spec.js create mode 100644 dgbuilder/test/red/settings_spec.js create mode 100644 dgbuilder/test/red/storage/index_spec.js create mode 100644 dgbuilder/test/red/storage/localfilesystem_spec.js create mode 100644 dgbuilder/test/red/ui_spec.js create mode 100644 dgbuilder/test/red/util_spec.js (limited to 'dgbuilder/test/red') 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("\n\n\n\n

this should be filtered out

\n\n\n\n\n"); + + var nodeId = list[0].id; + var nodeConfig = typeRegistry.getNodeConfig(nodeId); + nodeConfig.should.equal("\n\n\n\n

this should be filtered out

\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 @@ + + + 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 @@ + + + + + + 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 @@ + + + + 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 @@ + + + + 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 @@ + + + + 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 @@ + + + + +

this should be filtered out

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 @@ + + + + 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 @@ + + + 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]); + } + }); + }); +}); + -- cgit 1.2.3-korg