diff --git a/nodes/core/parsers/70-CSV.js b/nodes/core/parsers/70-CSV.js index 6ed9b2694..550f488bf 100644 --- a/nodes/core/parsers/70-CSV.js +++ b/nodes/core/parsers/70-CSV.js @@ -18,7 +18,7 @@ module.exports = function(RED) { "use strict"; function CSVNode(n) { RED.nodes.createNode(this,n); - this.template = n.temp.split(","); + this.template = (n.temp || "").split(","); this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r"); this.quo = '"'; this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r"); @@ -104,7 +104,7 @@ module.exports = function(RED) { if (line[i] === node.quo) { // if it's a quote toggle inside or outside f = !f; if (line[i-1] === node.quo) { k[j] += '\"'; } // if it's a quotequote then it's actually a quote - if ((line[i-1] !== node.sep) && (line[i+1] !== node.sep)) { k[j] += line[i]; } + //if ((line[i-1] !== node.sep) && (line[i+1] !== node.sep)) { k[j] += line[i]; } } else if ((line[i] === node.sep) && f) { // if we are outside of quote (ie valid separator if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } @@ -117,6 +117,7 @@ module.exports = function(RED) { } else if (f && ((line[i] === "\n") || (line[i] === "\r"))) { // handle multiple lines //console.log(j,k,o,k[j]); + if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) { if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); } else { k[j].replace(/\r$/,''); } @@ -164,6 +165,7 @@ module.exports = function(RED) { } else { node.warn("This node only handles csv strings or js objects."); } } + else { node.send(msg); } // If no payload - just pass it on. }); } RED.nodes.registerType("csv",CSVNode); diff --git a/test/nodes/core/analysis/72-sentiment_spec.js b/test/nodes/core/analysis/72-sentiment_spec.js new file mode 100644 index 000000000..a6a9469a7 --- /dev/null +++ b/test/nodes/core/analysis/72-sentiment_spec.js @@ -0,0 +1,95 @@ +/** + * Copyright 2015 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 sentimentNode = require("../../../../nodes/core/analysis/72-sentiment.js"); +var helper = require("../../helper.js"); + +describe('sentiment Node', function() { + + before(function(done) { + helper.startServer(done); + }); + + afterEach(function() { + helper.unload(); + }); + + it('should be loaded', function(done) { + var flow = [{id:"sentimentNode1", type:"sentiment", name: "sentimentNode" }]; + helper.load(sentimentNode, flow, function() { + var sentimentNode1 = helper.getNode("sentimentNode1"); + sentimentNode1.should.have.property('name', 'sentimentNode'); + done(); + }); + }); + + it('should add a positive score for good words', function(done) { + var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(sentimentNode, flow, function() { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + jn2.on("input", function(msg) { + msg.should.have.property('sentiment'); + msg.sentiment.should.have.property('score'); + msg.sentiment.score.should.be.a.number; + msg.sentiment.score.should.be.above(10); + done(); + }); + var testString = 'good, great, best, brilliant'; + jn1.receive({payload:testString}); + }); + }); + + it('should add a negative score for bad words', function(done) { + var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(sentimentNode, flow, function() { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + jn2.on("input", function(msg) { + msg.should.have.property('sentiment'); + msg.sentiment.should.have.property('score'); + msg.sentiment.score.should.be.a.number; + msg.sentiment.score.should.be.below(-10); + done(); + }); + var testString = 'bad, horrible, negative, awful'; + jn1.receive({payload:testString}); + }); + }); + + it('should allow you to override word scoring', function(done) { + var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(sentimentNode, flow, function() { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + jn2.on("input", function(msg) { + msg.should.have.property('sentiment'); + msg.sentiment.should.have.property('score'); + msg.sentiment.score.should.be.a.number; + msg.sentiment.score.should.equal(20); + done(); + }); + var testString = 'sick, wicked'; + var overrides = {'sick': 10, 'wicked': 10 }; + jn1.receive({payload:testString,overrides:overrides}); + }); + }); + +}); diff --git a/test/nodes/core/core/25-catch_spec.js b/test/nodes/core/core/25-catch_spec.js new file mode 100644 index 000000000..c894c1bae --- /dev/null +++ b/test/nodes/core/core/25-catch_spec.js @@ -0,0 +1,44 @@ +/** + * Copyright 2015 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 catchNode = require("../../../../nodes/core/core/25-catch.js"); +var helper = require("../../helper.js"); + +describe('catch Node', function() { + + afterEach(function() { + helper.unload(); + }); + + it('should output a message when called', function(done) { + var flow = [ { id:"n1", type:"catch", name:"catch", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(catchNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n1.should.have.property('name', 'catch'); + n2.on("input", function(msg) { + msg.should.be.a.Error; + msg.toString().should.equal("Error: big error"); + done(); + }); + var err = new Error("big error"); + n1.emit("input", err); + }); + }); + +}); diff --git a/test/nodes/core/core/89-trigger_spec.js b/test/nodes/core/core/89-trigger_spec.js new file mode 100644 index 000000000..c4d8b0af4 --- /dev/null +++ b/test/nodes/core/core/89-trigger_spec.js @@ -0,0 +1,300 @@ +/** + * Copyright 2015 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 triggerNode = require("../../../../nodes/core/core/89-trigger.js"); +var helper = require("../../helper.js"); + + +describe('trigger Node', function() { + + beforeEach(function(done) { + helper.startServer(done); + }); + + afterEach(function(done) { + helper.unload().then(function() { + helper.stopServer(done); + }); + }); + + it("should be loaded with correct defaults", function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", "wires":[[]]}]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + n1.should.have.property('name', 'triggerNode'); + n1.should.have.property('op1', 1); + n1.should.have.property('op2', 0); + n1.should.have.property('op1type', 'val'); + n1.should.have.property('op2type', 'val'); + n1.should.have.property('extend', "false"); + n1.should.have.property('units', 'ms'); + n1.should.have.property('duration', 250); + done(); + }); + }); + + it("should be able to set delay in seconds", function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", units:"s", duration:1, "wires":[[]]}]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + n1.should.have.property('duration', 1000); + done(); + }); + }); + + it("should be able to set delay in minutes", function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", units:"min", duration:1, "wires":[[]]}]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + n1.should.have.property('duration', 60000); + done(); + }); + }); + + it("should be able to set delay in hours", function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", units:"hr", duration:1, "wires":[[]]}]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + n1.should.have.property('duration', 3600000); + done(); + }); + }); + + it('should output 1 then 0 when triggered (default)', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + if (c === 0) { + msg.should.have.a.property("payload", 1); + c+=1; + } + else { + msg.should.have.a.property("payload", 0); + done(); + } + }); + n1.emit("input", {payload:null}); + }); + }); + + it('should ignore any other inputs while triggered if extend is false', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + if (c === 0) { + msg.should.have.a.property("payload", 1); + c+=1; + } + else { + msg.should.have.a.property("payload", 0); + } + }); + setTimeout( function() { + c.should.equal(1); + done(); + },500); + n1.emit("input", {payload:null}); + setTimeout( function() { + n1.emit("input", {payload:null}); + },75); + setTimeout( function() { + n1.emit("input", {payload:null}); + },150); + }); + }); + + it('should handle true and false as strings and delay of 0', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"true", op2:"false", duration:30, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + if (c === 0) { + msg.should.have.a.property("payload", true); + c+=1; + } + else { + msg.should.have.a.property("payload", false); + done(); + } + }); + n1.emit("input", {payload:null}); + }); + }); + + it('should be able to not output anything on first trigger', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"nul", op1:"true", op2:"false", duration:30, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.a.property("payload", false); + done(); + }); + n1.emit("input", {payload:null}); + }); + }); + + it('should be able to not output anything on second edge', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op2type:"nul", op1:"true", op2:"false", duration:30, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + msg.should.have.a.property("payload", true); + c += 1; + }); + setTimeout( function() { + c.should.equal(1); // should only have had one output. + done(); + },500); + n1.emit("input", {payload:null}); + }); + }); + + it('should be able to extend the delay', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op1:"false", op2:"true", duration:200, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + if (c === 0) { + msg.should.have.a.property("payload", "Hello"); + c += 1; + } + else { + msg.should.have.a.property("payload", true); + //console.log(Date.now() - ss); + (Date.now() - ss).should.be.greaterThan(299); + done(); + } + }); + var ss = Date.now(); + n1.emit("input", {payload:"Hello"}); + setTimeout( function() { + n1.emit("input", {payload:null}); + },100); + }); + }); + + it('should be able to extend the delay (but with no 2nd output)', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op2type:"nul", op1:"false", op2:"true", duration:200, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + if (c === 0) { + msg.should.have.a.property("payload", "Hello"); + c += 1; + } + else { + msg.should.have.a.property("payload", "World"); + //console.log(Date.now() - ss); + (Date.now() - ss).should.be.greaterThan(399); + done(); + } + }); + var ss = Date.now(); + n1.emit("input", {payload:"Hello"}); + setTimeout( function() { + n1.emit("input", {payload:"Error"}); + },100); + setTimeout( function() { + n1.emit("input", {payload:"World"}); + },400); + }); + }); + + it('should be able to apply mustache templates to payloads', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"{{payload}}", op2:"{{topic}}", duration:50, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + if (c === 0) { + msg.should.have.a.property("payload", "Hello"); + c+=1; + } + else { + msg.should.have.a.property("payload", "World"); + done(); + } + }); + n1.emit("input", {payload:"Hello",topic:"World"}); + }); + }); + + it('should handle string null as null', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op2type:"pay", op1:"null", op2:"null", duration:30, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + if (c === 0) { + msg.should.have.a.property("payload", null); + c+=1; + } + else { + msg.should.have.a.property("payload", "World"); + done(); + } + }); + n1.emit("input", {payload:"World"}); + }); + }); + + it('should be able to set infinite timeout, and clear timeout', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", duration:-5, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + msg.should.have.a.property("payload", 1); + }); + setTimeout( function() { + n1.emit("input", {reset:true}); + done(); + },500); + n1.emit("input", {payload:null}); + }); + }); + +}); diff --git a/test/nodes/core/core/98-unknown_spec.js b/test/nodes/core/core/98-unknown_spec.js new file mode 100644 index 000000000..ee2e73494 --- /dev/null +++ b/test/nodes/core/core/98-unknown_spec.js @@ -0,0 +1,36 @@ +/** + * Copyright 2015 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 unknown = require("../../../../nodes/core/core/98-unknown.js"); +var helper = require("../../helper.js"); + +describe('unknown Node', function() { + + afterEach(function() { + helper.unload(); + }); + + it('should be loaded', function(done) { + var flow = [{id:"n1", type:"unknown", name: "unknown" }]; + helper.load(unknown, flow, function() { + var n1 = helper.getNode("n1"); + n1.should.have.property('name', 'unknown'); + done(); + }); + }); + +}); diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js new file mode 100644 index 000000000..718780bf6 --- /dev/null +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -0,0 +1,270 @@ +/** + * Copyright 2015 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 csvNode = require("../../../../nodes/core/parsers/70-CSV.js"); +var helper = require("../../helper.js"); + +describe('CSV node', function() { + + before(function(done) { + helper.startServer(done); + }); + + afterEach(function() { + helper.unload(); + }); + + it('should be loaded with defaults', function(done) { + var flow = [{id:"csvNode1", type:"csv", name: "csvNode" }]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("csvNode1"); + n1.should.have.property('name', 'csvNode'); + n1.should.have.property('template', [ '' ]); + n1.should.have.property('sep', ','); + n1.should.have.property('quo', '"'); + n1.should.have.property('ret', '\n'); + n1.should.have.property('winflag', false); + n1.should.have.property('lineend', '\n'); + n1.should.have.property('multi', 'one'); + n1.should.have.property('hdrin', false); + done(); + }); + }); + + describe('csv to json', function() { + + it('should convert a simple csv string to a javascript object', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 }); + done(); + }); + var testString = "1,2,3,4"+String.fromCharCode(10); + n1.emit("input", {payload:testString}); + }); + }); + + it('should remove quotes and whitespace from template', function(done) { + var flow = [ { id:"n1", type:"csv", temp:'"a", "b" , " c "," d " ', wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 }); + done(); + }); + var testString = "1,2,3,4"+String.fromCharCode(10); + n1.emit("input", {payload:testString}); + }); + }); + + it('should create column names if no template provided', function(done) { + var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 }); + done(); + }); + var testString = "1,2,3,4"+String.fromCharCode(10); + n1.emit("input", {payload:testString}); + }); + }); + + it('should allow dropping of fields from the template', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload', { a: 1, d: 4 }); + done(); + }); + var testString = "1,2,3,4"+String.fromCharCode(10); + n1.emit("input", {payload:testString}); + }); + }); + + + it('should allow quotes in the input', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' }); + done(); + }); + var testString = '"1","-2","+3","04","-05",ab""cd,"with,a,comma"'+String.fromCharCode(10); + n1.emit("input", {payload:testString}); + }); + }); + + it('should be able to use the first line as a template', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + //console.log(msg); + if (c === 0) { + msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 }); + c += 1; + } + else { + msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 }); + done(); + } + }); + var testString = "w,x,y,z\n1,2,3,4\n\n5,6,7,8"; + n1.emit("input", {payload:testString}); + }); + }); + + it('should be able to output multiple lines as one array', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + //console.log(msg); + msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: 7, d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]); + done(); + }); + var testString = "1,2,3,4\n5,-6,07,+8\n9,0,a,b\nc,d,e,f"; + n1.emit("input", {payload:testString}); + }); + }); + }); + + describe('json object to csv', function() { + + it('should convert a simple object back to a csv', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload', '4,3,2,1\n'); + done(); + } + catch(e) { done(e); } + }); + var testJson = { d: 1, b: 3, c: 2, a: 4 }; + n1.emit("input", {payload:testJson}); + }); + }); + + it('should be able to include column names as first row', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload', 'a,b,c,d\r\n4,3,2,1\r\n'); + done(); + } + catch(e) { done(e); } + }); + var testJson = [{ d: 1, b: 3, c: 2, a: 4 }]; + n1.emit("input", {payload:testJson}); + }); + }); + + it('should handle quotes and sub-properties', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload', '{},"text,with,commas","This ""is"" a banana","{""sub"":""object""}"\n'); + done(); + } + catch(e) { done(e); } + }); + var testJson = { d: {sub:"object"}, b: "text,with,commas", c: 'This "is" a banana', a: {sub2:undefined} }; + n1.emit("input", {payload:testJson}); + }); + }); + + }); + + it('should just pass through if no payload provided', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('topic', { a: 4, b: 3, c: 2, d: 1 }); + msg.should.not.have.property('payload'); + + done(); + } + catch(e) { done(e); } + }); + var testJson = { d: 1, b: 3, c: 2, a: 4 }; + n1.emit("input", {topic:testJson}); + }); + }); + + it('should warn if provided a number or boolean', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "csv"; + }); + logEvents.should.have.length(2); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.startWith('This node only handles csv strings or js objects.'); + logEvents[1][0].should.have.a.property('msg'); + logEvents[1][0].msg.toString().should.startWith('This node only handles csv strings or js objects.'); + done(); + } catch(err) { + done(err); + } + },150); + n1.emit("input", {payload:1}); + n1.emit("input", {payload:true}); + }); + }); + +}); diff --git a/test/nodes/core/storage/50-file_spec.js b/test/nodes/core/storage/50-file_spec.js new file mode 100644 index 000000000..ba660b32b --- /dev/null +++ b/test/nodes/core/storage/50-file_spec.js @@ -0,0 +1,411 @@ +/** + * Copyright 2015 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 path = require('path'); +var fn = require('fs-extra'); +var mkdirp = require('mkdirp'); +var sinon = require("sinon"); +var fileNode = require("../../../../nodes/core/storage/50-file.js"); +var helper = require("../../helper.js"); +var log = require("../../../../red/log"); + +describe('file Nodes', function() { + + describe('file out Node', function() { + + var resourcesDir = path.join(__dirname,"..","..","..","resources"); + var fileToTest = path.join(resourcesDir,"50-file-test-file.txt"); + var wait = 150; + + beforeEach(function(done) { + //fn.writeFileSync(fileToTest, "File message line 1\File message line 2\n"); + helper.startServer(done); + }); + + afterEach(function(done) { + helper.unload().then(function() { + //fn.unlinkSync(fileToTest); + helper.stopServer(done); + }); + }); + + it('should be loaded', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":true}]; + helper.load(fileNode, flow, function() { + var fileNode1 = helper.getNode("fileNode1"); + fileNode1.should.have.property('name', 'fileNode'); + done(); + }); + }); + + it('should write to a file', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":true}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + n1.emit("input", {payload:"test"}); + setTimeout(function() { + var f = fn.readFileSync(fileToTest); + f.should.have.length(4); + fn.unlinkSync(fileToTest); + done(); + },wait); + }); + }); + + it('should append to a file and add newline', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + n1.emit("input", {payload:"test2"}); // string + setTimeout(function() { + n1.emit("input", {payload:true}); // boolean + },50); + setTimeout(function() { + var f = fn.readFileSync(fileToTest).toString(); + f.should.have.length(11); + f.should.equal("test2\ntrue\n"); + done(); + },wait); + }); + }); + + it('should warn if msg.filename tries to override node', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + n1.emit("input", {payload:{a:1,b:2}, filename:"/tmp/foo"}); // object + setTimeout(function() { + var f = fn.readFileSync(fileToTest).toString(); + f.should.have.length(25); + f.should.equal("test2\ntrue\n{\"a\":1,\"b\":2}\n"); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file"; + }); + //console.log(logEvents); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.startWith("Warning: msg"); + done(); + },wait); + }); + }); + + it('should use msg.filename if filename not set in node', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + n1.emit("input", {payload:"fine", filename:fileToTest}); + setTimeout(function() { + var f = fn.readFileSync(fileToTest).toString(); + f.should.have.length(5); + f.should.equal("fine\n"); + done(); + },wait); + }); + }); + + it('should warn and not delete the file if msg.delete set', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":true}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + n1.emit("input", {payload:"fine",delete:true}); + setTimeout(function() { + try { + var f = fn.readFileSync(fileToTest).toString(); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file"; + }); + //console.log(logEvents); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.startWith("Warning: Invalid"); + done(); + } + catch(e) { + done(); + } + },wait); + }); + }); + + it('should be able to delete the file', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":"delete"}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + n1.emit("input", {payload:"fine"}); + setTimeout(function() { + try { + var f = fn.readFileSync(fileToTest).toString(); + f.should.not.equal("fine"); + //done(); + } + catch(e) { + e.code.should.equal("ENOENT"); + done(); + } + },wait); + }); + }); + + it('should warn if filename not set', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":false}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + n1.emit("input", {payload:"nofile"}); + setTimeout(function() { + try { + var f = fn.readFileSync(fileToTest).toString(); + f.should.not.equal("fine"); + //done(); + } + catch(e) { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file"; + }); + //console.log(logEvents); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.equal("No filename specified"); + done(); + } + },wait); + }); + }); + + it('ignore a null payload', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + n1.emit("input", {payload:null}); + setTimeout(function() { + try { + var f = fn.readFileSync(fileToTest).toString(); + f.should.not.equal("fine"); + //done(); + } + catch(e) { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file"; + }); + //console.log(logEvents); + logEvents.should.have.length(0); + done(); + } + },wait); + }); + }); + + it('should fail to write to a ro file', function(done) { + // Stub file write so we can make writes fail + var fs = require('fs'); + var spy = sinon.stub(fs, 'writeFile', function(arg,arg2,arg3,arg4){ arg4(new Error("Stub error message")); }); + + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":true}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file"; + }); + //console.log(logEvents); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.startWith("Failed to write"); + done(); + } + catch(e) { done(e); } + finally { fs.writeFile.restore(); } + },wait); + n1.receive({payload:"test"}); + }); + }); + + it('should fail to append to a ro file', function(done) { + // Stub file write so we can make writes fail + var fs = require('fs'); + var spy = sinon.stub(fs, 'appendFile', function(arg,arg2,arg3,arg4){ arg4(new Error("Stub error message")); }); + + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file"; + }); + //console.log(logEvents); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.startWith("Failed to append"); + done(); + } + catch(e) { done(e); } + finally { fs.appendFile.restore(); } + },wait); + n1.receive({payload:"test2"}); + }); + }); + + it('should cope with failing to delete a file', function(done) { + // Stub file write so we can make writes fail + var fs = require('fs'); + var spy = sinon.stub(fs, 'unlink', function(arg,arg2){ arg2(new Error("Stub error message")); }); + + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":"delete"}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + setTimeout(function() { + try { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file"; + }); + //console.log(logEvents); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.startWith("Failed to delete"); + done(); + } + catch(e) { done(e); } + finally { fs.unlink.restore(); } + },wait); + n1.receive({payload:"test2"}); + }); + }); + + }); + + + describe('file in Node', function() { + + var resourcesDir = path.join(__dirname,"..","..","..","resources"); + var fileToTest = path.join(resourcesDir,"50-file-test-file.txt"); + var wait = 150; + + beforeEach(function(done) { + fn.writeFileSync(fileToTest, "File message line 1\File message line 2\n"); + helper.startServer(done); + }); + + afterEach(function(done) { + helper.unload().then(function() { + fn.unlinkSync(fileToTest); + helper.stopServer(done); + }); + }); + + it('should be loaded', function(done) { + var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"utf8"}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileInNode1"); + n1.should.have.property('name', 'fileInNode'); + done(); + }); + }); + + it('should read in a file and output a buffer', function(done) { + var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]}, + {id:"n2", type:"helper"}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileInNode1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload'); + msg.payload.should.have.length(39).and.be.a.Buffer; + msg.payload.toString().should.equal("File message line 1\File message line 2\n"); + done(); + }); + n1.receive({payload:""}); + }); + }); + +// Commented out to make build pass on node v.0.8 - reinstate when we drop 0.8 support... + //it('should read in a file and output a utf8 string', function(done) { + //var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"utf8", wires:[["n2"]]}, + //{id:"n2", type:"helper"}]; + //helper.load(fileNode, flow, function() { + //var n1 = helper.getNode("fileInNode1"); + //var n2 = helper.getNode("n2"); + //n2.on("input", function(msg) { + //msg.should.have.property('payload'); + //msg.payload.should.have.length(39).and.be.a.string; + //msg.payload.should.equal("File message line 1\File message line 2\n"); + //done(); + //}); + //n1.receive({payload:""}); + //}); + //}); + + it('should warn if msg.props try to overide', function(done) { + var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]}, + {id:"n2", type:"helper"}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileInNode1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload'); + msg.payload.should.have.length(39).and.be.a.Buffer; + msg.payload.toString().should.equal("File message line 1\File message line 2\n"); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file in"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.startWith("Warning: msg "); + done(); + }); + n1.receive({payload:"",filename:"foo.txt"}); + }); + }); + + it('should warn if no filename set', function(done) { + var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "format":""}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileInNode1"); + setTimeout(function() { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file in"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.equal("No filename specified"); + done(); + },wait); + n1.receive({}); + }); + }); + + it('should handle a file read error', function(done) { + var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":"badfile", "format":""}]; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileInNode1"); + setTimeout(function() { + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "file in"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.toString().should.equal("Error: ENOENT, open 'badfile'"); + done(); + },wait); + n1.receive({payload:""}); + }); + }); + + }); + +});