From c7b62aed916f5cd2974ed3bb8faffaf8f7353af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathana=C3=ABl=20L=C3=A9caud=C3=A9?= Date: Wed, 29 Aug 2018 12:20:04 -0400 Subject: [PATCH 1/5] JSON schema: add draft-06 support (via $schema keyword) --- nodes/core/parsers/70-JSON.js | 1 + 1 file changed, 1 insertion(+) diff --git a/nodes/core/parsers/70-JSON.js b/nodes/core/parsers/70-JSON.js index eb0bec63c..234a742c6 100644 --- a/nodes/core/parsers/70-JSON.js +++ b/nodes/core/parsers/70-JSON.js @@ -19,6 +19,7 @@ module.exports = function(RED) { const Ajv = require('ajv'); const ajv = new Ajv({allErrors: true, schemaId: 'auto'}); ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json')); + ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')); function JSONNode(n) { RED.nodes.createNode(this,n); From 40d81358f4c3fed49d992e2a08c4f6a401ff2218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathana=C3=ABl=20L=C3=A9caud=C3=A9?= Date: Wed, 29 Aug 2018 13:36:28 -0400 Subject: [PATCH 2/5] JSON schema: perform validation when obj -> obj or str -> str --- nodes/core/parsers/70-JSON.js | 35 +++++++- test/nodes/core/parsers/70-JSON_spec.js | 105 ++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/nodes/core/parsers/70-JSON.js b/nodes/core/parsers/70-JSON.js index 234a742c6..bc5d0269a 100644 --- a/nodes/core/parsers/70-JSON.js +++ b/nodes/core/parsers/70-JSON.js @@ -30,6 +30,16 @@ module.exports = function(RED) { this.compiledSchema = null; var node = this; + + this.validateMessage = function(msg) { + if (this.compiledSchema(msg[node.property])) { + node.send(msg); + } else { + msg.schemaError = this.compiledSchema.errors; + node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg); + } + } + this.on("input", function(msg) { var validate = false; if (msg.schema) { @@ -66,7 +76,17 @@ module.exports = function(RED) { } catch(e) { node.error(e.message,msg); } } else { - node.send(msg); + // If node.action is str and value is str + if (validate) { + if (this.compiledSchema(JSON.parse(msg[node.property]))) { + node.send(msg); + } else { + msg.schemaError = this.compiledSchema.errors; + node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg); + } + } else { + node.send(msg); + } } } else if (typeof value === "object") { @@ -85,13 +105,22 @@ module.exports = function(RED) { RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent)); node.send(msg); } - } catch(e) { node.error(RED._("json.errors.dropped-error")); } } else { node.warn(RED._("json.errors.dropped-object")); } } else { - node.send(msg); + // If node.action is obj and value is object + if (validate) { + if (this.compiledSchema(value)) { + node.send(msg); + } else { + msg.schemaError = this.compiledSchema.errors; + node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg); + } + } else { + node.send(msg); + } } } else { node.warn(RED._("json.errors.dropped")); } diff --git a/test/nodes/core/parsers/70-JSON_spec.js b/test/nodes/core/parsers/70-JSON_spec.js index ba913b1e7..4f6e4beb2 100644 --- a/test/nodes/core/parsers/70-JSON_spec.js +++ b/test/nodes/core/parsers/70-JSON_spec.js @@ -265,6 +265,23 @@ describe('JSON node', function() { }); }); + it('should pass an object if provided a valid object and schema and action is object', function(done) { + var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(jsonNode, flow, function() { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + jn2.on("input", function(msg) { + should.equal(msg.payload.number, 3); + should.equal(msg.payload.string, "allo"); + done(); + }); + var obj = {"number": 3, "string": "allo"}; + var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; + jn1.receive({payload:obj, schema:schema}); + }); + }); + it('should pass a string if provided a valid object and schema', function(done) { var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, {id:"jn2", type:"helper"}]; @@ -281,6 +298,22 @@ describe('JSON node', function() { }); }); + it('should pass a string if provided a valid JSON string and schema and action is string', function(done) { + var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(jsonNode, flow, function() { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + jn2.on("input", function(msg) { + should.equal(msg.payload, '{"number":3,"string":"allo"}'); + done(); + }); + var jsonString = '{"number":3,"string":"allo"}'; + var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; + jn1.receive({payload:jsonString, schema:schema}); + }); + }); + it('should log an error if passed an invalid object and valid schema', function(done) { var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, {id:"jn2", type:"helper"}]; @@ -305,6 +338,78 @@ describe('JSON node', function() { }); }); + it('should log an error if passed an invalid object and valid schema and action is object', function(done) { + var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(jsonNode, flow, function() { + try { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; + var obj = {"number": "foo", "string": 3}; + jn1.receive({payload:obj, schema:schema}); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { + done(err); + } + }); + }); + + it('should log an error if passed an invalid JSON string and valid schema', function(done) { + var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(jsonNode, flow, function() { + try { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; + var jsonString = '{"number":"Hello","string":3}'; + jn1.receive({payload:jsonString, schema:schema}); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { + done(err); + } + }); + }); + + it('should log an error if passed an invalid JSON string and valid schema and action is string', function(done) { + var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]}, + {id:"jn2", type:"helper"}]; + helper.load(jsonNode, flow, function() { + try { + var jn1 = helper.getNode("jn1"); + var jn2 = helper.getNode("jn2"); + var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}}; + var jsonString = '{"number":"Hello","string":3}'; + jn1.receive({payload:jsonString, schema:schema}); + var logEvents = helper.log().args.filter(function(evt) { + return evt[0].type == "json"; + }); + logEvents.should.have.length(1); + logEvents[0][0].should.have.a.property('msg'); + logEvents[0][0].msg.should.equal("json.errors.schema-error: data.number should be number, data.string should be string"); + logEvents[0][0].should.have.a.property('level',helper.log().ERROR); + done(); + } catch(err) { + done(err); + } + }); + }); + it('should log an error if passed a valid object and invalid schema', function(done) { var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, {id:"jn2", type:"helper"}]; From 4cdd7978cf5699ccab1f4ec6d03907cb66f1e166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathana=C3=ABl=20L=C3=A9caud=C3=A9?= Date: Wed, 29 Aug 2018 13:40:37 -0400 Subject: [PATCH 3/5] JSON schema: remove unused function --- nodes/core/parsers/70-JSON.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/nodes/core/parsers/70-JSON.js b/nodes/core/parsers/70-JSON.js index bc5d0269a..4e1e51e47 100644 --- a/nodes/core/parsers/70-JSON.js +++ b/nodes/core/parsers/70-JSON.js @@ -31,15 +31,6 @@ module.exports = function(RED) { var node = this; - this.validateMessage = function(msg) { - if (this.compiledSchema(msg[node.property])) { - node.send(msg); - } else { - msg.schemaError = this.compiledSchema.errors; - node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg); - } - } - this.on("input", function(msg) { var validate = false; if (msg.schema) { From 8e9815fb91e04a9665ca8b336c07cb8c10e88690 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 30 Aug 2018 20:47:39 +0100 Subject: [PATCH 4/5] TCP-request node - only write payload to close #1869 --- nodes/core/io/31-tcpin.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nodes/core/io/31-tcpin.js b/nodes/core/io/31-tcpin.js index 26593647a..47b4bed7c 100644 --- a/nodes/core/io/31-tcpin.js +++ b/nodes/core/io/31-tcpin.js @@ -33,9 +33,7 @@ module.exports = function(RED) { */ const enqueue = (queue, item) => { // drop msgs from front of queue if size is going to be exceeded - if (queue.size() === msgQueueSize) { - queue.shift(); - } + if (queue.size() === msgQueueSize) { queue.shift(); } queue.push(item); return queue; }; @@ -646,7 +644,7 @@ module.exports = function(RED) { } else if (!clients[connection_id].connecting && clients[connection_id].connected) { if (clients[connection_id] && clients[connection_id].client) { - clients[connection_id].client.write(dequeue(clients[connection_id].msgQueue)); + clients[connection_id].client.write(dequeue(clients[connection_id].msgQueue).payload); } } }); From 69448c73290ca59ae8430ff6d64bfa41f77bfc4a Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 30 Aug 2018 20:54:03 +0100 Subject: [PATCH 5/5] pi nodes - increase test coverage slightly --- test/nodes/core/hardware/36-rpi-gpio_spec.js | 65 +++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/test/nodes/core/hardware/36-rpi-gpio_spec.js b/test/nodes/core/hardware/36-rpi-gpio_spec.js index c11ea254c..871dabcf9 100644 --- a/test/nodes/core/hardware/36-rpi-gpio_spec.js +++ b/test/nodes/core/hardware/36-rpi-gpio_spec.js @@ -15,7 +15,8 @@ **/ var should = require("should"); -var rpi = require("../../../../nodes/core/hardware/36-rpi-gpio.js"); +var rpiNode = require("../../../../nodes/core/hardware/36-rpi-gpio.js"); +var statusNode = require("../../../../nodes/core/core/25-status.js"); var helper = require("node-red-node-test-helper"); var fs = require("fs"); @@ -50,7 +51,7 @@ describe('RPI GPIO Node', function() { it('should load Input node', function(done) { var flow = [{id:"n1", type:"rpi-gpio in", name:"rpi-gpio in" }]; - helper.load(rpi, flow, function() { + helper.load(rpiNode, flow, function() { var n1 = helper.getNode("n1"); n1.should.have.property('name', 'rpi-gpio in'); try { @@ -69,7 +70,7 @@ describe('RPI GPIO Node', function() { it('should load Output node', function(done) { var flow = [{id:"n1", type:"rpi-gpio out", name:"rpi-gpio out" }]; - helper.load(rpi, flow, function() { + helper.load(rpiNode, flow, function() { var n1 = helper.getNode("n1"); n1.should.have.property('name', 'rpi-gpio out'); try { @@ -86,4 +87,62 @@ describe('RPI GPIO Node', function() { }); }); + + it('should read a dummy value high (not on Pi)', function(done) { + var flow = [{id:"n1", type:"rpi-gpio in", pin:"7", intype:"up", debounce:"25", read:true, wires:[["n2"]] }, + {id:"n2", type:"helper"}]; + helper.load(rpiNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('topic', 'pi/7'); + msg.should.have.property('payload', 1); + done(); + } catch(err) { + done(err); + } + }); + }); + }); + + it('should read a dummy value low (not on Pi)', function(done) { + var flow = [{id:"n1", type:"rpi-gpio in", pin:"11", intype:"down", debounce:"25", read:true, wires:[["n2"]] }, + {id:"n2", type:"helper"}]; + helper.load(rpiNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('topic', 'pi/11'); + msg.should.have.property('payload', 0); + done(); + } catch(err) { + done(err); + } + }); + }); + }); + + it('should be able preset out to a dummy value (not on Pi)', function(done) { + var flow = [{id:"n1", type:"rpi-gpio out", pin:"7", out:"out", level:"0", set:true, freq:"", wires:[], z:"1"}, + {id:"n2", type:"status", scope:null, wires:[["n3"]], z:"1"}, + {id:"n3", type:"helper", z:"1"}]; + helper.load([rpiNode,statusNode], flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + n3.on("input", function(msg) { + try { + msg.should.have.property('status'); + msg.status.should.have.property('text', "rpi-gpio.status.na"); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"1"}); + }); + }); + });