Compare commits

..

25 Commits

Author SHA1 Message Date
Steve-Mcl
20a63a3292 fix tests after adding 'set by msg.payloadHandling' 2022-11-29 13:50:39 +00:00
Steve-Mcl
3a5cdbc8ae improve UI around payloadHandling 2022-11-29 13:38:09 +00:00
Stephen McLaughlin
3ed5969e87 add msg.payloadHandling for get requests 2022-11-28 19:31:44 +00:00
Nick O'Leary
14c362d4ba Merge pull request #3942 from node-red-hitachi/fix-watch-test
fix watch node test on MacOS/ARM
2022-11-07 21:12:55 +00:00
Hiroyasu Nishiyama
fce43b4e1d fix condition for platform check 2022-11-05 14:50:14 +09:00
Hiroyasu Nishiyama
1d547500e8 fix watch node test on MacOS/ARM 2022-11-05 14:30:32 +09:00
Nick O'Leary
946def022f Merge pull request #3922 from node-red/fix-httpreq-tests
Fix httprequest tests to be more lenient on error message
2022-10-26 00:40:47 +01:00
Nick O'Leary
c62a101635 Fix httprequest tests to be more lenient on error message 2022-10-16 23:10:57 +01:00
Nick O'Leary
32999ffa84 Merge pull request #3906 from node-red/Fix-for-csv-undefined-property
Fix for CSV undefined property
2022-10-04 15:38:37 +01:00
Nick O'Leary
f06c53f1f1 Merge pull request #3905 from node-red/mqtt-followups
Fix birth topic handling in MQTT node
2022-10-04 15:36:49 +01:00
Nick O'Leary
a9eec28360 Merge pull request #3884 from node-red/fix-auto-complete
Fix autocomplete entry for responseUrl
2022-10-04 15:35:10 +01:00
Nick O'Leary
5cda972872 Merge pull request #3890 from kazuhitoyokoi/master-fixmqttproperty
Fix pull-down menus of MQTT configuration node
2022-10-04 15:34:50 +01:00
Nick O'Leary
087946876b Merge pull request #3907 from boahc077/github_actions_token_permission
ci: add minimum GitHub token permissions for workflows
2022-10-04 15:32:47 +01:00
Nick O'Leary
318f0f1b7e Merge pull request #3899 from node-red/fix-change-self-overwrite
Fix change node overwriting msg with itself
2022-10-04 15:29:23 +01:00
Ashish Kurmi
87e7f3a61c ci: add minimum GitHub token permissions for workflows
Signed-off-by: Ashish Kurmi <akurmi@stepsecurity.io>
2022-10-02 11:16:13 -07:00
Dave Conway-Jones
e724f216bf Fix for CSV undefined property
to close #3900 main issue
and add tests
(other fix is commented out but no tests)
2022-09-30 13:48:48 +01:00
Steve-Mcl
b0abba15a6 remove dud code instead of commenting 2022-09-29 19:08:46 +01:00
Steve-Mcl
81b4874a7c fix new test and fix bug found in previous PR 2022-09-29 19:05:53 +01:00
Steve-Mcl
f11b9c1e18 add test bad birth topic
part of #3865
2022-09-29 13:12:15 +01:00
Steve-Mcl
e15ecc00ce remove old unused code (5y+ not used) 2022-09-29 13:11:25 +01:00
Dave Conway-Jones
3e4c45ac6a Fix change node overwriting msg with itself
and add test
to close #3891
2022-09-22 20:22:11 +01:00
Kazuhito Yokoi
f872e2ab80 Add icon to typedInput in MQTT node 2022-09-18 19:11:33 +09:00
Kazuhito Yokoi
a81b1aa0cb Support i18n in MQTT node property 2022-09-18 17:10:19 +09:00
Kazuhito Yokoi
efc0f1ab91 Fix default values for MQTT retain settings 2022-09-18 16:24:25 +09:00
Nick O'Leary
9fd4989142 Fix autocomplete entry for responseUrl
Fixes #3883
2022-09-15 21:22:55 +01:00
23 changed files with 280 additions and 73 deletions

View File

@@ -5,6 +5,9 @@ on:
release:
types: [published]
permissions:
contents: read
jobs:
generate:
name: 'Update node-red-docker image'

View File

@@ -6,8 +6,14 @@ on:
pull_request:
branches: [ master, dev ]
permissions:
contents: read
jobs:
build:
permissions:
checks: write # for coverallsapp/github-action to create new checks
contents: read # for actions/checkout to fetch code
runs-on: ubuntu-latest
strategy:
matrix:

View File

@@ -146,7 +146,7 @@
{ value: "reset", source: ["delay","trigger","join","rbe"] },
{ value: "responseCookies", source: ["http request"] },
{ value: "responseTopic", source: ["mqtt"] },
{ value: "responseURL", source: ["http request"] },
{ value: "responseUrl", source: ["http request"] },
{ value: "restartTimeout", source: ["join"] },
{ value: "retain", source: ["mqtt"] },
{ value: "schema", source: ["json"] },

View File

@@ -318,7 +318,7 @@ module.exports = function(RED) {
}
var r = node.rules[currentRule];
if (r.t === "move") {
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1) && (r.p !== r.to)) {
applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt},(err,msg) => {
applyRule(msg,{t:"delete", p:r.p, pt:r.pt}, (err,msg) => {
completeApplyingRules(msg,currentRule,done);

View File

@@ -421,7 +421,11 @@
<script type="text/javascript">
(function() {
var typedInputNoneOpt = { value: 'none', label: '', hasValue: false };
var typedInputNoneOpt = {
value: 'none',
label: RED._("node-red:mqtt.label.none"),
hasValue: false
};
var makeTypedInputOpt = function(value){
return {
value: value,
@@ -436,7 +440,11 @@
makeTypedInputOpt("text/csv"),
makeTypedInputOpt("text/html"),
makeTypedInputOpt("text/plain"),
{value:"other", label:""}
{
value: "other",
label: RED._("node-red:mqtt.label.other"),
icon: "red/images/typedInput/az.svg"
}
];
function getDefaultContentType(value) {
@@ -499,17 +507,17 @@
cleansession: {value: true},
birthTopic: {value:"", validate:validateMQTTPublishTopic},
birthQos: {value:"0"},
birthRetain: {value:false},
birthRetain: {value:"false"},
birthPayload: {value:""},
birthMsg: { value: {}},
closeTopic: {value:"", validate:validateMQTTPublishTopic},
closeQos: {value:"0"},
closeRetain: {value:false},
closeRetain: {value:"false"},
closePayload: {value:""},
closeMsg: { value: {}},
willTopic: {value:"", validate:validateMQTTPublishTopic},
willQos: {value:"0"},
willRetain: {value:false},
willRetain: {value:"false"},
willPayload: {value:""},
willMsg: { value: {}},
userProps: { value: ""},

View File

@@ -459,7 +459,6 @@ module.exports = function(RED) {
if(!opts || typeof opts !== "object") {
return; //nothing to change, simply return
}
const originalBrokerURL = node.brokerurl;
//apply property changes (only if the property exists in the opts object)
setIfHasProperty(opts, node, "url", init);
@@ -468,7 +467,6 @@ module.exports = function(RED) {
setIfHasProperty(opts, node, "clientid", init);
setIfHasProperty(opts, node, "autoConnect", init);
setIfHasProperty(opts, node, "usetls", init);
setIfHasProperty(opts, node, "usews", init);
setIfHasProperty(opts, node, "verifyservercert", init);
setIfHasProperty(opts, node, "compatmode", init);
setIfHasProperty(opts, node, "protocolVersion", init);
@@ -571,9 +569,6 @@ module.exports = function(RED) {
if (typeof node.usetls === 'undefined') {
node.usetls = false;
}
if (typeof node.usews === 'undefined') {
node.usews = false;
}
if (typeof node.verifyservercert === 'undefined') {
node.verifyservercert = false;
}
@@ -1000,7 +995,7 @@ module.exports = function(RED) {
node.client.publish(msg.topic, msg.payload, options, function (err) {
if (done) {
done(err)
} else {
} else if(err) {
node.error(err, msg)
}
})

View File

@@ -33,11 +33,12 @@
</div>
<div class="form-row node-input-paytoqs-row">
<label for="node-input-paytoqs"><span data-i18n="common.label.payload"></span></label>
<label for="node-input-paytoqs"><span data-i18n="httpin.label.paytoqs.label"></span></label>
<select id="node-input-paytoqs" style="width: 70%;">
<option value="ignore" data-i18n="httpin.label.paytoqs.ignore"></option>
<option value="query" data-i18n="httpin.label.paytoqs.query"></option>
<option value="body" data-i18n="httpin.label.paytoqs.body"></option>
<option value="setby" data-i18n="httpin.label.paytoqs.setby"></option>
</select>
</div>
@@ -290,7 +291,7 @@
RED.tray.resize();
});
$("#node-input-method").on("change", function() {
if ($(this).val() == "GET") {
if ($(this).val() == "GET" || $(this).val() == "use") {
$(".node-input-paytoqs-row").show();
} else {
$(".node-input-paytoqs-row").hide();

View File

@@ -16,7 +16,7 @@
module.exports = function(RED) {
"use strict";
const got = require("got");
const got = require("got").default;
const {CookieJar} = require("tough-cookie");
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
const FormData = require('form-data');
@@ -69,8 +69,6 @@ in your Node-RED user directory (${RED.settings.userDir}).
var nodeUrl = n.url;
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
var nodeMethod = n.method || "GET";
var paytoqs = false;
var paytobody = false;
var redirectList = [];
var sendErrorsToCatch = n.senderr;
node.headers = n.headers || [];
@@ -78,15 +76,12 @@ in your Node-RED user directory (${RED.settings.userDir}).
if (n.tls) {
var tlsNode = RED.nodes.getNode(n.tls);
}
this.ret = n.ret || "txt";
this.authType = n.authType || "basic";
if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
else { this.reqTimeout = 120000; }
if (n.paytoqs === true || n.paytoqs === "query") { paytoqs = true; }
else if (n.paytoqs === "body") { paytobody = true; }
node.ret = n.ret || "txt";
node.authType = n.authType || "basic";
if (RED.settings.httpRequestTimeout) { node.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
else { node.reqTimeout = 120000; }
node.insecureHTTPParser = n.insecureHTTPParser
node.paytoqs = n.paytoqs
var prox, noprox;
if (process.env.http_proxy) { prox = process.env.http_proxy; }
@@ -196,20 +191,26 @@ in your Node-RED user directory (${RED.settings.userDir}).
}
}
var method = nodeMethod.toUpperCase() || "GET";
let method = nodeMethod.toUpperCase() || "GET";
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
node.warn(RED._("common.errors.nooverride"));
}
if (msg.method && n.method && (n.method === "use")) {
method = msg.method.toUpperCase(); // use the msg parameter
method = msg.method.toUpperCase(); // use the msg parameter
}
/** @type {boolean|'query'|'body'|'setby'} */
let payloadHandling = node.paytoqs
if (msg.payloadHandling && payloadHandling && (payloadHandling !== "setby")) { // warn if override option not set
node.warn(RED._("common.errors.nooverride"));
}
if (msg.payloadHandling && payloadHandling && (payloadHandling === "setby")) {
payloadHandling = msg.payloadHandling // use the msg parameter
}
if (payloadHandling === true) { payloadHandling = "query" }
// var isHttps = (/^https/i.test(url));
/** @type {import('got').Options} */
var opts = {};
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
// Had to remove this to get http->https redirect to work
// opts.defaultPort = isHttps?443:80;
opts.timeout = node.reqTimeout;
opts.throwHttpErrors = false;
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
@@ -472,7 +473,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
}
if (method == 'GET' && typeof msg.payload !== "undefined" && paytoqs) {
if (method == "GET" && typeof msg.payload !== "undefined" && payloadHandling === "query") {
if (typeof msg.payload === "object") {
try {
if (url.indexOf("?") !== -1) {
@@ -481,18 +482,16 @@ in your Node-RED user directory (${RED.settings.userDir}).
url += "?" + querystring.stringify(msg.payload);
}
} catch(err) {
node.error(RED._("httpin.errors.invalid-payload"),msg);
nodeDone();
return;
}
} else {
node.error(RED._("httpin.errors.invalid-payload"),msg);
nodeDone();
return;
}
} else if ( method == "GET" && typeof msg.payload !== "undefined" && paytobody) {
} else if ( method == "GET" && typeof msg.payload !== "undefined" && payloadHandling === "body") {
opts.allowGetBody = true;
if (typeof msg.payload === "object") {
opts.body = JSON.stringify(msg.payload);

View File

@@ -110,7 +110,12 @@ module.exports = function(RED) {
if (msg.payload[s].hasOwnProperty(p)) {
/* istanbul ignore else */
if (typeof msg.payload[s][p] !== "object") {
var q = "" + msg.payload[s][p];
// Fix to honour include null values flag
//if (typeof msg.payload[s][p] !== "object" || (node.include_null_values === true && msg.payload[s][p] === null)) {
var q = "";
if (msg.payload[s][p] !== undefined) {
q += msg.payload[s][p];
}
if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
q = q.replace(/"/g, '""');
ou += node.quo + q + node.quo + node.sep;
@@ -130,9 +135,12 @@ module.exports = function(RED) {
ou += node.sep;
}
else {
var p = RED.util.ensureString(RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']"));
var p = RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']");
/* istanbul ignore else */
if (p === "undefined") { p = ""; }
if (p === undefined) { p = ""; }
// fix to honour include null values flag
//if (p === null && node.include_null_values !== true) { p = "";}
p = RED.util.ensureString(p);
if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""');
ou += node.quo + p + node.quo + node.sep;

View File

@@ -449,9 +449,11 @@
"headers": "Kopfzeilen",
"other": "andere",
"paytoqs": {
"label": "Payload (GET)",
"ignore": "Ignorieren",
"query": "Anfügen an query-string-Parameter",
"body": "Senden als request-body"
"body": "Senden als request-body",
"setby": "Durch msg.payloadHandling festgelegt"
},
"utf8String": "UTF-8-String",
"binaryBuffer": "Binärer Buffer",

View File

@@ -446,7 +446,9 @@
"staticTopic": "Subscribe to single topic",
"dynamicTopic": "Dynamic subscription",
"auto-connect": "Connect automatically",
"auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode."
"auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode.",
"none": "none",
"other": "other"
},
"sections-label": {
"birth-message": "Message sent on connection (birth message)",
@@ -507,9 +509,11 @@
"headers": "Headers",
"other": "other",
"paytoqs": {
"label": "Payload (GET)",
"ignore": "Ignore",
"query": "Append to query-string parameters",
"body": "Send as request body"
"body": "Send as request body",
"setby": "- set by msg.payloadHandling -"
},
"utf8String": "UTF8 string",
"binaryBuffer": "binary buffer",

View File

@@ -30,6 +30,10 @@
<dd>If set, can be used to send cookies with the request.</dd>
<dt class="optional">payload</dt>
<dd>Sent as the body of the request.</dd>
<dt class="optional">payloadHandling <span class="property-type">string</span></dt>
<dd>Only valid with GET requests. If set to <b>"- use msg.payloadHandling -"</b> in the node configuration, this property
indicates how the <code>payload</code> will be sent. <code>msg.payloadHandling</code> should contain either
<code>"query"</code> or <code>"body"</code> otherwise the payload will not be sent with the GET request</dd>
<dt class="optional">rejectUnauthorized</dt>
<dd>If set to <code>false</code>, allows requests to be made to https sites that use
self signed certificates.</dd>

View File

@@ -446,7 +446,9 @@
"staticTopic": "1つのトピックを購読",
"dynamicTopic": "動的な購読",
"auto-connect": "自動接続",
"auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。"
"auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。",
"none": "なし",
"other": "その他"
},
"sections-label": {
"birth-message": "接続時の送信メッセージ(Birthメッセージ)",
@@ -507,9 +509,11 @@
"headers": "ヘッダ",
"other": "その他",
"paytoqs": {
"label": "ペイロード (GET)",
"ignore": "無視",
"query": "クエリパラメータに追加",
"body": "リクエストボディとして送信"
"body": "リクエストボディとして送信",
"setby": "- msg.payloadHandlingに定義 -"
},
"utf8String": "UTF8文字列",
"binaryBuffer": "バイナリバッファ",

View File

@@ -387,7 +387,13 @@
"status": "상태코드",
"headers": "헤더",
"other": "그 외",
"paytoqs" : "msg.payload를 쿼리 파라미터에 추가",
"paytoqs": {
"label": "페이로드(GET)",
"ignore": "무시",
"query": "msg.payload를 쿼리 파라미터에 추가",
"body": "본문에 msg.payload 추가",
"setby": "- msg.payloadHandling에 의해 설정됨 -"
},
"utf8String": "UTF8문자열",
"binaryBuffer": "바이너리 버퍼",
"jsonObject": "JSON오브젝트",

View File

@@ -411,9 +411,11 @@
"headers": "Заголовки",
"other": "другое",
"paytoqs": {
"label": "Данные (GET)",
"ignore": "Игнорировать",
"query": "Добавлять к параметрам строки запроса",
"body": "Отправлять как тело запроса"
"body": "Отправлять как тело запроса",
"setby": "- устанавливается через msg.payloadHandling -"
},
"utf8String": "Строка UTF8",
"binaryBuffer": "двоичный буфер",

View File

@@ -407,7 +407,13 @@
"status": "状态码",
"headers": "头",
"other": "其他",
"paytoqs": "将msg.payload附加为查询字符串参数",
"paytoqs": {
"label": "有效负载(仅限 GET",
"ignore": "漠视",
"query": "将msg.payload附加为查询字符串参数",
"body": "在请求正文中发送负载",
"setby": "- 用 msg.payloadHandling 设定 -"
},
"utf8String": "UTF8格式的字符串",
"binaryBuffer": "二进制buffer",
"jsonObject": "解析的JSON对象",

View File

@@ -411,7 +411,13 @@
"status": "狀態碼",
"headers": "Header",
"other": "其他",
"paytoqs": "將msg.payload附加為查詢字符串參數",
"paytoqs": {
"label": "有效負載(僅限 GET",
"ignore": "漠視",
"query": "將msg.payload附加為查詢字符串參數",
"body": "在請求正文中發送負載",
"setby": "- 用 msg.payloadHandling 設定 -"
},
"utf8String": "UTF8格式的字符串",
"binaryBuffer": "二進制buffer",
"jsonObject": "解析的JSON對象",

View File

@@ -818,16 +818,6 @@ function handlePreRoute(flow, sendEvent, reportError) {
})
}
function deliverMessageToDestination(sendEvent) {
if (sendEvent?.destination?.node) {
try {
sendEvent.destination.node.receive(sendEvent.msg);
} catch(err) {
Log.error(`Error delivering message to node:${sendEvent.destination.node._path} [${sendEvent.destination.node.type}]`)
Log.error(err.stack)
}
}
}
function handlePreDeliver(flow,sendEvent, reportError) {
// preDeliver - the local router has identified the node it is going to send to. At this point, the message has been cloned if needed.
hooks.trigger("preDeliver",sendEvent,(err) => {
@@ -837,10 +827,15 @@ function handlePreDeliver(flow,sendEvent, reportError) {
} else if (err !== false) {
if (asyncMessageDelivery) {
setImmediate(function() {
deliverMessageToDestination(sendEvent)
if (sendEvent.destination.node) {
sendEvent.destination.node.receive(sendEvent.msg);
}
})
} else {
deliverMessageToDestination(sendEvent)
if (sendEvent.destination.node) {
sendEvent.destination.node.receive(sendEvent.msg);
}
}
// postDeliver - the message has been dispatched to be delivered asynchronously (unless the sync delivery flag is set, in which case it would be continue as synchronous delivery)
hooks.trigger("postDeliver", sendEvent, function(err) {

View File

@@ -1717,6 +1717,24 @@ describe('change Node', function() {
changeNode1.receive({topic:{foo:{bar:1}}, payload:"String"});
});
});
it('moves the value of a message property object to itself', function(done) {
var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload","tot":"msg"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
msg.should.have.property('payload');
msg.payload.should.equal("bar");
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:"bar"});
});
});
it('moves the value of a message property object to a sub-property', function(done) {
var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.foo","tot":"msg"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];

View File

@@ -294,6 +294,19 @@ describe('HTTP Request Node', function() {
url: req.originalUrl
});
})
testApp.get('/getBodyParams', function(req,res) {
// Either body-parser or express is discarding the body OR
// GOT never sent a body. (there is nothing in params/query/body)
// Oddly, if we set options.json (instead of options.body) in the GOT
// request, then req.body will have the values!
//I suspect the GOT lib *is* sending body on a GET request since an
// error is thrown if we try to set options.body on a GET request without
// setting options.allowGetBody to true.
res.json({
// body:JSON.parse(req.body), //req.body is empty!
url: req.originalUrl
});
})
testApp.get('/returnError/:code', function(req,res) {
res.status(parseInt(req.params.code)).json({gotError:req.params.code});
})
@@ -1117,6 +1130,89 @@ describe('HTTP Request Node', function() {
});
});
it('should allow the payload to be sent in the body for a GET request', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",paytoqs:"body",ret:"obj",url:getTestURL('/getBodyParams')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
const payload = {a:"one",b:true,c:3}
n2.on("input", function(msg) {
try {
// Either Express does not deliver the body of a GET request OR GOT doesn't send it!
// That means we cannot test this! Oddly, if the HTTP-Req node sets options.json instead
// of options.body, then the body is delivered.
// msg.should.have.property('payload',{
// body:payload,
// url: '/getBodyParams'
// });
msg.should.have.property('payload').and.be.an.Object()
msg.payload.should.have.property('url', '/getBodyParams')
msg.should.have.property('statusCode',200);
msg.should.have.property('headers');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:payload});
});
});
it('should allow the message to specify that the payload be append to querystring for a GET request', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"use",paytoqs:"setby",ret:"obj",url:getTestURL('/getQueryParams')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload',{
query:{a:'1',b:'2',c:'3'},
url: '/getQueryParams?a=1&b=2&c=3'
});
msg.should.have.property('statusCode',200);
msg.should.have.property('headers');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:{a:1,b:2,c:3},method:"get",payloadHandling:'query'});
});
});
it('should allow the message to specify that the payload be sent in the body for a GET request', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"use",paytoqs:"setby",ret:"obj",url:getTestURL('/getBodyParams')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
const payload = {a:"one",b:true,c:3}
n2.on("input", function(msg) {
try {
// Either Express does not deliver the body of a GET request OR GOT doesn't send it!
// That means we cannot test this! Oddly, if the HTTP-Req node sets options.json instead
// of options.body, then the body is delivered.
// msg.should.have.property('payload',{
// payload: payload,
// url: '/getBodyParams'
// });
msg.should.have.property('payload').and.be.an.Object()
msg.payload.should.have.property('url', '/getBodyParams')
msg.should.have.property('statusCode',200);
msg.should.have.property('headers');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:payload,method:"get",payloadHandling:'body'});
});
});
it('should send a msg for non-2xx response status - 400', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/returnError/400')},
{id:"n2", type:"helper"}];
@@ -1529,7 +1625,7 @@ describe('HTTP Request Node', function() {
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
//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
@@ -2281,7 +2377,7 @@ describe('HTTP Request Node', function() {
let port = testPort++
let server;
before(function() {
server = net.createServer(function (socket) {
socket.write("HTTP/1.0 200\nContent-Type: text/plain\n\nHelloWorld")
@@ -2291,7 +2387,7 @@ describe('HTTP Request Node', function() {
server.listen(port,'127.0.0.1', function(err) {
})
});
after(function() {
server.close()
});
@@ -2322,14 +2418,14 @@ describe('HTTP Request Node', function() {
var n2 = helper.getNode("n2");
n2.on('input', function(msg) {
try{
msg.payload.should.equal(`RequestError: Parse Error: Missing expected CR after header value : http://localhost:${port}/`)
msg.payload.should.match(/RequestError: Parse Error/)
done()
} catch (err) {
done(err)
}
})
n1.receive({payload: 'foo'})
});
});
}

View File

@@ -489,6 +489,39 @@ describe('MQTT Nodes', function () {
}
testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks);
});
itConditional('should safely discard bad birth topic', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const baseTopic = nextTopic();
const brokerOptions = {
protocolVersion: 4,
birthTopic: baseTopic + "#", // a publish topic should never have a wildcard
birthPayload: "broker connected",
birthQos: 2,
}
const options = {};
const hooks = { done: null, 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
}
options.expectMsg = null;
try {
testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks);
done()
} catch(err) {
done(e)
}
});
itConditional('should publish close message', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
@@ -646,12 +679,13 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo
const mqttBroker = helper.getNode(brokerOptions.id);
const mqttIn = helper.getNode(nodes.mqtt_in.id);
const mqttOut = helper.getNode(nodes.mqtt_out.id);
let afterLoadHandled = false;
let afterLoadHandled = false, finished = false;
if (hooks.afterLoad) {
afterLoadHandled = hooks.afterLoad(helperNode, mqttBroker, mqttIn, mqttOut)
}
if (!afterLoadHandled) {
helperNode.on("input", function (msg) {
finished = true
try {
compareMsgToExpected(msg, expectMsg);
if (hooks.done) { hooks.done(); }
@@ -676,10 +710,12 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo
}
})
.catch((e) => {
if(finished) { return }
if (hooks.done) { hooks.done(e); }
else { throw e; }
});
} catch (err) {
if(finished) { return }
if (hooks.done) { hooks.done(err); }
else { throw err; }
}

View File

@@ -693,19 +693,20 @@ describe('CSV node', function() {
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,,e,f", wires:[["n2"]] },
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e,f,g,h,i,j,k", 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("GOT",msg)
try {
msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld"\n');
msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld",,,undefined,null,null\n');
done();
}
catch(e) { done(e); }
});
var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld" };
var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld", h:undefined, i:"undefined",j:null,k:"null" };
n1.emit("input", {payload:testJson});
});
});
@@ -717,13 +718,14 @@ describe('CSV node', function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
// console.log("GOT",msg)
try {
msg.should.have.property('payload', '1,foo,"ba""r","di,ng"\n');
msg.should.have.property('payload', '1,foo,"ba""r","di,ng",,undefined,null\n');
done();
}
catch(e) { done(e); }
});
var testJson = { d:1, b:"foo", c:"ba\"r", a:"di,ng" };
var testJson = { d:1, b:"foo", c:"ba\"r", a:"di,ng", e:undefined, f:"undefined", g:null,h:"null" };
n1.emit("input", {payload:testJson});
});
});

View File

@@ -15,11 +15,14 @@
**/
var fs = require("fs-extra");
var os = require("os");
var path = require("path");
var should = require("should");
var helper = require("node-red-node-test-helper");
var watchNode = require("nr-test-utils").require("@node-red/nodes/core/storage/23-watch.js");
var arch = os.arch();
var platform = os.platform();
describe('watch Node', function() {
this.timeout(5000);
@@ -89,7 +92,10 @@ describe('watch Node', function() {
msg.should.have.property('payload', result.payload);
msg.should.have.property('type', result.type);
if('size' in result) {
msg.should.have.property('size', result.size);
if (!((arch === "arm64") && (platform === "darwin"))) {
// On OSX/ARM, two change events occur and first event do not reflect file size change. So ignore size field in the case.
msg.should.have.property('size', result.size);
}
}
count++;
if(count === len) {