Merge branch 'dev' into diagnostics

This commit is contained in:
Stephen McLaughlin
2022-04-27 12:08:32 +01:00
committed by GitHub
210 changed files with 26309 additions and 21234 deletions

View File

@@ -1500,6 +1500,50 @@ describe('HTTP Request Node', function() {
n1.receive({payload:{foo:"bar"}, headers: { 'content-type': 'text/plain', "x-node-red-request-node":"INVALID_SUM"}});
});
});
it('should use ui headers', function (done) {
const flow = [
{
id: "n1", type: "http request", wires: [["n2"]], method: "GET", ret: "obj", url: getTestURL('/rawHeaders'),
"headers": [
{ "keyType": "Accept", "keyValue": "", "valueType": "application/json", "valueValue": "" },//set "Accept" to "application/json"
{ "keyType": "Content-Type", "keyValue": "", "valueType": "application/json", "valueValue": "" },//overwrite msg.headers['content-type'] with UI header 'Content-Type']
{ "keyType": "msg", "keyValue": "dynamicHeaderName", "valueType": "msg", "valueValue": "dynamicHeaderValue" }, //dynamic msg.dynamicHeaderName/msg.dynamicHeaderValue
{ "keyType": "other", "keyValue": "static-header-name", "valueType": "other", "valueValue": "static-header-value" }, //static "other" header and value
{ "keyType": "Location", "keyValue": "", "valueType": "other", "valueValue": "" }, //delete "Location" header (initially set in msg.headers['location'] by passing empty string for value
]
},
{ id: "n2", type: "helper" }];
helper.load(httpRequestNode, flow, function () {
const n1 = helper.getNode("n1");
const n2 = helper.getNode("n2");
n2.on("input", function (msg) {
try {
msg.should.have.property('statusCode',200);
msg.payload.should.have.property('headers');
//msg.headers['Accept'] should be set by Flow UI
msg.payload.headers.should.have.property('Accept').which.startWith('application/json');
//msg.headers['content-type'] should be updated to 'Content-Type' by the value set in the Flow UI
msg.payload.headers.should.have.property('Content-Type').which.startWith('application/json');
//msg.dynamicHeaderName should be present in headers with the value of msg.dynamicHeaderValue
msg.payload.headers.should.have.property('dyn-header-name').which.startWith('dyn-header-value');
//static (custom) header set in Flow UI should be present
msg.payload.headers.should.have.property('static-header-name').which.startWith('static-header-value');
//msg.headers['location'] should be deleted because Flow UI "Location" header has a blank value
//ensures headers with matching characters but different case are eliminated
msg.payload.headers.should.not.have.property('location');
msg.payload.headers.should.not.have.property('Location');
done();
} catch (err) {
done(err);
}
});
// Pass in a headers property with a "content-type" & "location" header
// Pass in dynamicHeaderName & dynamicHeaderValue properties to set the Flow UI `msg.xxx` header entries
n1.receive({ payload: { foo: "bar" }, dynamicHeaderName: "dyn-header-name", dynamicHeaderValue: "dyn-header-value", headers: { 'content-type': 'text/plain', 'location': 'london' } });
});
});
});
describe('protocol', function() {
@@ -1976,8 +2020,14 @@ describe('HTTP Request Node', function() {
describe('file-upload', function() {
it('should upload a file', function(done) {
var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'POST',ret:'obj',url:getTestURL('/file-upload')},
{id:"n2", type:"helper"}];
const flow = [
{
id: 'n1', type: 'http request', wires: [['n2']], method: 'POST', ret: 'obj', url: getTestURL('/file-upload'), headers: [
{ "keyType": "Content-Type", "keyValue": "", "valueType": "multipart/form-data", "valueValue": "" }
]
},
{ id: "n2", type: "helper" }
];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -1995,9 +2045,6 @@ describe('HTTP Request Node', function() {
}
});
n1.receive({
headers: {
'content-type':'multipart/form-data'
},
payload: {
file: {
value: Buffer.from("Hello World"),

View File

@@ -4,6 +4,7 @@
"use strict";
const should = require("should");
const helper = require("node-red-node-test-helper");
const { doesNotThrow } = require("should");
const mqttNodes = require("nr-test-utils").require("@node-red/nodes/core/network/10-mqtt.js");
const BROKER_HOST = process.env.MQTT_BROKER_SERVER || "localhost";
const BROKER_PORT = process.env.MQTT_BROKER_PORT || 1883;
@@ -92,7 +93,8 @@ describe('MQTT Nodes', function () {
options.expectMsg = Object.assign({}, options.sendMsg);
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, { done: done });
});
itConditional('should send JSON and receive string (auto)', function (done) {
//Prior to V3, "auto" mode would only parse to string or buffer.
itConditional('should send JSON and receive string (auto mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
@@ -104,7 +106,44 @@ describe('MQTT Nodes', function () {
options.expectMsg = Object.assign({}, options.sendMsg);
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, { done: done });
})
itConditional('should send JSON and receive string (utf8)', function (done) {
//In V3, "auto" mode should try to parse JSON, then string and fall back to buffer
itConditional('should send JSON and receive object (auto-detect mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
options.sendMsg = {
topic: nextTopic(),
payload: '{"prop":"value1", "num":1}',
qos: 1
}
options.expectMsg = Object.assign({}, options.sendMsg);
options.expectMsg.payload = JSON.parse(options.sendMsg.payload);
testSendRecv({}, { datatype: "auto-detect", topicType: "static" }, {}, options, { done: done });
})
itConditional('should send invalid JSON and receive string (auto mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
options.sendMsg = {
topic: nextTopic(),
payload: '{prop:"value3", "num":3}'// send invalid JSON ...
}
options.expectMsg = Object.assign({}, options.sendMsg);//expect same payload
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, { done: done });
});
itConditional('should send invalid JSON and receive string (auto-detect mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
options.sendMsg = {
topic: nextTopic(),
payload: '{prop:"value3", "num":3}'// send invalid JSON ...
}
options.expectMsg = Object.assign({}, options.sendMsg);//expect same payload
testSendRecv({}, { datatype: "auto-detect", topicType: "static" }, {}, options, { done: done });
});
itConditional('should send JSON and receive string (utf8 mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
@@ -116,7 +155,7 @@ describe('MQTT Nodes', function () {
options.expectMsg = Object.assign({}, options.sendMsg);
testSendRecv({}, { datatype: "utf8", topicType: "static" }, {}, options, { done: done });
});
itConditional('should send JSON and receive Object (json)', function (done) {
itConditional('should send JSON and receive Object (json mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
@@ -127,7 +166,31 @@ describe('MQTT Nodes', function () {
options.expectMsg = Object.assign({}, options.sendMsg, { payload: { "prop": "value3", "num": 3 } });//expect an object
testSendRecv({}, { datatype: "json", topicType: "static" }, {}, options, { done: done });
});
itConditional('should send String and receive Buffer (buffer)', function (done) {
itConditional('should send invalid JSON and raise error (json mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
options.sendMsg = {
topic: nextTopic(),
payload: '{prop:"value3", "num":3}', // send invalid JSON ...
}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
helperNode.on("input", function (msg) {
try {
msg.should.have.a.property("error").type("object");
msg.error.should.have.a.property("source").type("object");
msg.error.source.should.have.a.property("id", mqttIn.id);
done();
} catch (err) {
done(err)
}
});
return true; //handled
}
testSendRecv({}, { datatype: "json", topicType: "static" }, {}, options, hooks);
});
itConditional('should send String and receive Buffer (buffer mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
@@ -138,7 +201,7 @@ describe('MQTT Nodes', function () {
options.expectMsg = Object.assign({}, options.sendMsg, { payload: Buffer.from(options.sendMsg.payload) });//expect Buffer.from(msg.payload)
testSendRecv({}, { datatype: "buffer", topicType: "static" }, {}, options, { done: done });
});
itConditional('should send utf8 Buffer and receive String (auto)', function (done) {
itConditional('should send utf8 Buffer and receive String (auto mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
@@ -149,7 +212,7 @@ describe('MQTT Nodes', function () {
options.expectMsg = Object.assign({}, options.sendMsg, { payload: "x y z" });//set expected payload to "x y z"
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, { done: done });
});
itConditional('should send non utf8 Buffer and receive Buffer (auto)', function (done) {
itConditional('should send non utf8 Buffer and receive Buffer (auto mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
@@ -158,7 +221,7 @@ describe('MQTT Nodes', function () {
topic: nextTopic(),
payload: Buffer.from([0xC0, 0xC1, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF]) //non valid UTF8
}
options.expectMsg = Object.assign({}, options.sendMsg);
options.expectMsg = Object.assign({}, options.sendMsg, {payload: Buffer.from([0xC0, 0xC1, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF])});
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, hooks);
});
itConditional('should send/receive all v5 flags and settings', function (done) {
@@ -168,16 +231,16 @@ describe('MQTT Nodes', function () {
const options = {}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
options.sendMsg = {
topic: t + "/command", payload: Buffer.from("v5"), qos: 1, retain: true,
topic: t + "/command", payload: Buffer.from('{"version":"v5"}'), qos: 1, retain: true,
responseTopic: t + "/response",
userProperties: { prop1: "val1" },
contentType: "application/json",
contentType: "text/plain",
correlationData: Buffer.from([1, 2, 3]),
payloadFormatIndicator: true,
messageExpiryInterval: 2000,
}
options.expectMsg = Object.assign({}, options.sendMsg);
options.expectMsg.payload = options.expectMsg.payload.toString(); //auto mode + payloadFormatIndicator should make a string
options.expectMsg.payload = options.expectMsg.payload.toString(); //auto mode + payloadFormatIndicator + contentType: "text/plain" should make a string
delete options.expectMsg.payloadFormatIndicator; //Seems mqtt.js only publishes payloadFormatIndicator the will msg
const inOptions = {
datatype: "auto", topicType: "static",
@@ -185,6 +248,109 @@ describe('MQTT Nodes', function () {
}
testSendRecv({ protocolVersion: 5 }, inOptions, {}, options, hooks);
});
itConditional('should send regular string with v5 media type "text/plain" and receive a string (auto mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
options.sendMsg = {
topic: nextTopic(), payload: "abc", contentType: "text/plain"
}
options.expectMsg = Object.assign({}, options.sendMsg);
testSendRecv({ protocolVersion: 5 }, { datatype: "auto", topicType: "static" }, {}, options, hooks);
});
itConditional('should send JSON with v5 media type "text/plain" and receive a string (auto mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
options.sendMsg = {
topic: nextTopic(), payload: '{"prop":"val"}', contentType: "text/plain"
}
options.expectMsg = Object.assign({}, options.sendMsg);
testSendRecv({ protocolVersion: 5 }, { datatype: "auto", topicType: "static" }, {}, options, hooks);
});
itConditional('should send JSON with v5 media type "text/plain" and receive a string (auto-detect mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
options.sendMsg = {
topic: nextTopic(), payload: '{"prop":"val"}', contentType: "text/plain"
}
options.expectMsg = Object.assign({}, options.sendMsg);
testSendRecv({ protocolVersion: 5 }, { datatype: "auto-detect", topicType: "static" }, {}, options, hooks);
});
itConditional('should send JSON with v5 media type "application/json" and receive an object (auto-detect mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
options.sendMsg = {
topic: nextTopic(), payload: '{"prop":"val"}', contentType: "application/json",
}
options.expectMsg = Object.assign({}, options.sendMsg, { payload: JSON.parse(options.sendMsg.payload)});
testSendRecv({ protocolVersion: 5 }, { datatype: "auto-detect", topicType: "static" }, {}, options, hooks);
});
itConditional('should send invalid JSON with v5 media type "application/json" and raise an error (auto mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
options.sendMsg = {
topic: nextTopic(),
payload: '{prop:"value3", "num":3}', contentType: "application/json", // send invalid JSON ...
}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
helperNode.on("input", function (msg) {
try {
msg.should.have.a.property("error").type("object");
msg.error.should.have.a.property("source").type("object");
msg.error.source.should.have.a.property("id", mqttIn.id);
done();
} catch (err) {
done(err)
}
});
return true; //handled
}
testSendRecv({ protocolVersion: 5 }, { datatype: "auto", topicType: "static" }, {}, options, hooks);
});
itConditional('should send buffer with v5 media type "application/json" and receive an object (auto-detect mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
options.sendMsg = {
topic: nextTopic(), payload: Buffer.from([0x7b,0x22,0x70,0x72,0x6f,0x70,0x22,0x3a,0x22,0x76,0x61,0x6c,0x22,0x7d]), contentType: "application/json",
}
options.expectMsg = Object.assign({}, options.sendMsg, { payload: {"prop":"val"}});
testSendRecv({ protocolVersion: 5 }, { datatype: "auto-detect", topicType: "static" }, {}, options, hooks);
});
itConditional('should send buffer with v5 media type "text/plain" and receive a string (auto mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
options.sendMsg = {
topic: nextTopic(), payload: Buffer.from([0x7b,0x22,0x70,0x72,0x6f,0x70,0x22,0x3a,0x22,0x76,0x61,0x6c,0x22,0x7d]), contentType: "text/plain",
}
options.expectMsg = Object.assign({}, options.sendMsg, { payload: '{"prop":"val"}'});
testSendRecv({ protocolVersion: 5 }, { datatype: "auto", topicType: "static" }, {}, options, hooks);
});
itConditional('should send buffer with v5 media type "application/zip" and receive a buffer (auto mode)', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
options.sendMsg = {
topic: nextTopic(), payload: Buffer.from([0x7b,0x22,0x70,0x72,0x6f,0x70,0x22,0x3a,0x22,0x76,0x61,0x6c,0x22,0x7d]), contentType: "application/zip",
}
options.expectMsg = Object.assign({}, options.sendMsg, { payload: Buffer.from([0x7b,0x22,0x70,0x72,0x6f,0x70,0x22,0x3a,0x22,0x76,0x61,0x6c,0x22,0x7d])});
testSendRecv({ protocolVersion: 5 }, { datatype: "auto", topicType: "static" }, {}, options, hooks);
});
itConditional('should subscribe dynamically via action', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
@@ -463,14 +629,16 @@ function buildBasicMQTTSendRecvFlow(brokerOptions, inOptions, outOptions) {
const inNode = buildMQTTInNode(inOptions.id, inOptions.name, inOptions.broker || broker.id, inOptions.topic, inOptions, ["helper.node"]);
const outNode = buildMQTTOutNode(outOptions.id, outOptions.name, outOptions.broker || broker.id, outOptions.topic, outOptions);
const helper = buildNode("helper", "helper.node", "helper_node", {});
const catchNode = buildNode("catch", "catch.node", "catch_node", {"scope": ["mqtt.in"]}, ["helper.node"]);
return {
nodes: {
[broker.name]: broker,
[inNode.name]: inNode,
[outNode.name]: outNode,
[helper.name]: helper,
[catchNode.name]: catchNode,
},
flow: [broker, inNode, outNode, helper]
flow: [broker, inNode, outNode, helper, catchNode]
}
}

View File

@@ -127,6 +127,12 @@ describe('TCP in Node', function() {
testTCP0(flow, ["foo\nbar"], ["fo", "bar"], done);
});
it('should recv data (Stream/String/Delimiter:o\\n) and reattach o', function(done) {
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"stream", datatype:"utf8", newline:"o\n", trim:true, topic:"", base64:false, wires:[["n2"]] },
{id:"n2", type:"helper"}];
testTCP0(flow, ["foo\nbar"], ["foo\n", "bar"], done);
});
it('should recv data (Stream/String/No delimiter)', function(done) {
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"stream", datatype:"utf8", newline:"", topic:"", base64:false, wires:[["n2"]] },
{id:"n2", type:"helper"}];

View File

@@ -278,7 +278,22 @@ describe('TCP Request Node', function() {
payload: "bar<A>\nfoo",
topic: 'boo'
}], {
payload: "ACK:foobar<A>",
payload: "ACK:foobar",
topic: 'boo'
}, done);
});
it('should send & receive, then keep connection, and split return strings and reattach delimiter', function(done) {
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", ret:"string", newline:"<A>\\n", trim:true, wires:[["n2"]] },
{id:"n2", type:"helper"}];
testTCPMany(flow, [{
payload: "foo",
topic: 'boo'
}, {
payload: "bar<A>\nfoo",
topic: 'boo'
}], {
payload: "ACK:foobar<A>\n",
topic: 'boo'
}, done);
});

View File

@@ -45,7 +45,7 @@ describe('JSON node', function() {
msg.payload.employees[0].should.have.property('lastName', 'Smith');
done();
});
var jsonString = '{"employees":[{"firstName":"John", "lastName":"Smith"}]}';
var jsonString = ' {"employees":[{"firstName":"John", "lastName":"Smith"}]}\r\n ';
jn1.receive({payload:jsonString,topic: "bar"});
});
});
@@ -63,7 +63,7 @@ describe('JSON node', function() {
msg.payload.employees[0].should.have.property('lastName', 'Smith');
done();
});
var jsonString = Buffer.from('{"employees":[{"firstName":"John", "lastName":"Smith"}]}');
var jsonString = Buffer.from(' {"employees":[{"firstName":"John", "lastName":"Smith"}]}\r\n ');
jn1.receive({payload:jsonString,topic: "bar"});
});
});

View File

@@ -56,7 +56,7 @@ describe('XML node', function() {
should.equal(msg.payload.employees.lastName[0], 'Smith');
done();
});
var string = '<employees><firstName>John</firstName><lastName>Smith</lastName></employees>';
var string = ' <employees><firstName>John</firstName><lastName>Smith</lastName></employees>\r\n ';
n1.receive({payload:string,topic: "bar"});
});
});
@@ -76,7 +76,7 @@ describe('XML node', function() {
should.equal(msg.foo.employees.lastName[0], 'Smith');
done();
});
var string = '<employees><firstName>John</firstName><lastName>Smith</lastName></employees>';
var string = ' <employees><firstName>John</firstName><lastName>Smith</lastName></employees>\r\n ';
n1.receive({foo:string,topic: "bar"});
});
});
@@ -96,7 +96,7 @@ describe('XML node', function() {
should.equal(msg.payload.employees.lastName[0], 'Smith');
done();
});
var string = '<employees><firstName>John</firstName><lastName>Smith</lastName></employees>';
var string = ' <employees><firstName>John</firstName><lastName>Smith</lastName></employees>\r\n ';
n1.receive({payload:string, topic:"bar", options:{trim:true}});
});
});

View File

@@ -92,7 +92,23 @@ describe("api/auth/strategies", function() {
tokenCreate.restore();
}
});
});
it('Uses provided token on authentication success and token provided',function(done) {
userAuthentication = sinon.stub(Users,"authenticate").callsFake(function(username,password) {
return Promise.resolve({username:"user",permissions:"*",token:"123456"});
});
strategies.passwordTokenExchange({id:"myclient"},"user","password","read",function(err,token) {
try {
should.not.exist(err);
token.should.equal("123456");
done();
} catch(e) {
done(e);
}
});
});
});

View File

@@ -379,7 +379,6 @@ describe('red/runtime/nodes/credentials', function() {
credentials.export().then(function(result) {
result.should.have.a.property("$");
settings.should.not.have.a.property("_credentialSecret");
// reset everything - but with _credentialSecret still set
credentials.init(runtime);
// load the freshly encrypted version
@@ -445,6 +444,21 @@ describe('red/runtime/nodes/credentials', function() {
});
});
it('handles bad credentials object - resets credentials', function(done) {
settings = {
credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a"
};
// {"node":{user1:"abc",password1:"123"}}
var cryptedFlows = {"BADKEY":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
credentials.init(runtime);
credentials.load(cryptedFlows).then(function() {
done();
}).catch(function(err) {
err.should.have.property('code','credentials_load_failed');
done();
});
});
it('handles unavailable settings - leaves creds unencrypted', function(done) {
var runtime = {
log: log,

View File

@@ -63,7 +63,7 @@ describe("red/nodes/index", function() {
var runtime = {
settings: settings,
storage: storage,
log: {debug:function() {}, warn:function() {}},
log: {debug:function() {}, warn:function() {}, _: function() {}},
events: new EventEmitter()
};