node-red
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,897 @@
|
||||
|
||||
/* These tests are only supposed to be executed at development time (for now)*/
|
||||
|
||||
"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;
|
||||
//By default, MQTT tests are disabled. Set ENV VAR NR_MQTT_TESTS to "1" or "true" to enable
|
||||
const skipTests = process.env.NR_MQTT_TESTS != "true" && process.env.NR_MQTT_TESTS != "1";
|
||||
|
||||
describe('MQTT Nodes', function () {
|
||||
|
||||
before(function (done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
try {
|
||||
helper.unload();
|
||||
} catch (error) { }
|
||||
});
|
||||
|
||||
it('should be loaded and have default values (MQTT V4)', function (done) {
|
||||
this.timeout = 2000;
|
||||
const { flow, nodes } = buildBasicMQTTSendRecvFlow({ id: "mqtt.broker", name: "mqtt_broker", autoConnect: false }, { id: "mqtt.in", topic: "in_topic" }, { id: "mqtt.out", topic: "out_topic" });
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
try {
|
||||
const mqttIn = helper.getNode("mqtt.in");
|
||||
const mqttOut = helper.getNode("mqtt.out");
|
||||
const mqttBroker = helper.getNode("mqtt.broker");
|
||||
|
||||
should(mqttIn).be.type("object", "mqtt in node should be an object")
|
||||
mqttIn.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node
|
||||
mqttIn.should.have.property('datatype', 'utf8'); //default: 'utf8'
|
||||
mqttIn.should.have.property('isDynamic', false); //default: false
|
||||
mqttIn.should.have.property('inputs', 0); //default: 0
|
||||
mqttIn.should.have.property('qos', 2); //default: 2
|
||||
mqttIn.should.have.property('topic', "in_topic");
|
||||
mqttIn.should.have.property('wires', [["helper.node"]]);
|
||||
|
||||
should(mqttOut).be.type("object", "mqtt out node should be an object")
|
||||
mqttOut.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node
|
||||
mqttOut.should.have.property('topic', "out_topic");
|
||||
|
||||
should(mqttBroker).be.type("object", "mqtt broker node should be an object")
|
||||
mqttBroker.should.have.property('broker', BROKER_HOST);
|
||||
mqttBroker.should.have.property('port', BROKER_PORT);
|
||||
mqttBroker.should.have.property('brokerurl');
|
||||
mqttBroker.should.have.property('autoUnsubscribe', true); //default: true
|
||||
mqttBroker.should.have.property('autoConnect', false);//Set "autoConnect:false" in brokerOptions
|
||||
mqttBroker.should.have.property('options');
|
||||
mqttBroker.options.should.have.property('clean', true);
|
||||
mqttBroker.options.should.have.property('clientId');
|
||||
mqttBroker.options.clientId.should.containEql('nodered');
|
||||
mqttBroker.options.should.have.property('keepalive').type("number");
|
||||
mqttBroker.options.should.have.property('reconnectPeriod').type("number");
|
||||
//as this is not a v5 connection, ensure v5 properties are not present
|
||||
mqttBroker.options.should.not.have.property('protocolVersion', 5);
|
||||
mqttBroker.options.should.not.have.property('properties');
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
it('should be loaded and have default values (MQTT V5)', function (done) {
|
||||
this.timeout = 2000;
|
||||
const { flow, nodes } = buildBasicMQTTSendRecvFlow({ id: "mqtt.broker", name: "mqtt_broker", autoConnect: false, cleansession: false, clientid: 'clientid', keepalive: 35, sessionExpiry: '6000', protocolVersion: '5', userProps: {"prop": "val"}}, { id: "mqtt.in", topic: "in_topic" }, { id: "mqtt.out", topic: "out_topic" });
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
try {
|
||||
const mqttIn = helper.getNode("mqtt.in");
|
||||
const mqttOut = helper.getNode("mqtt.out");
|
||||
const mqttBroker = helper.getNode("mqtt.broker");
|
||||
|
||||
should(mqttIn).be.type("object", "mqtt in node should be an object")
|
||||
mqttIn.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node
|
||||
mqttIn.should.have.property('datatype', 'utf8'); //default: 'utf8'
|
||||
mqttIn.should.have.property('isDynamic', false); //default: false
|
||||
mqttIn.should.have.property('inputs', 0); //default: 0
|
||||
mqttIn.should.have.property('qos', 2); //default: 2
|
||||
mqttIn.should.have.property('topic', "in_topic");
|
||||
mqttIn.should.have.property('wires', [["helper.node"]]);
|
||||
|
||||
should(mqttOut).be.type("object", "mqtt out node should be an object")
|
||||
mqttOut.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node
|
||||
mqttOut.should.have.property('topic', "out_topic");
|
||||
|
||||
should(mqttBroker).be.type("object", "mqtt broker node should be an object")
|
||||
mqttBroker.should.have.property('broker', BROKER_HOST);
|
||||
mqttBroker.should.have.property('port', BROKER_PORT);
|
||||
mqttBroker.should.have.property('brokerurl');
|
||||
mqttBroker.should.have.property('autoUnsubscribe', true);
|
||||
mqttBroker.should.have.property('autoConnect', false); //Set "autoConnect:false" in brokerOptions
|
||||
mqttBroker.should.have.property('options');
|
||||
mqttBroker.options.should.have.property('clean', false);
|
||||
mqttBroker.options.should.have.property('clientId', 'clientid');
|
||||
mqttBroker.options.should.have.property('keepalive').type("number", 35);
|
||||
mqttBroker.options.should.have.property('reconnectPeriod').type("number");
|
||||
//as this IS a v5 connection, ensure v5 properties are not present
|
||||
mqttBroker.options.should.have.property('protocolVersion', 5);
|
||||
mqttBroker.options.should.have.property('properties');
|
||||
mqttBroker.options.properties.should.have.property('sessionExpiryInterval');
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (skipTests) {
|
||||
it('skipping MQTT tests. Set env var "NR_MQTT_TESTS=true" to enable. Requires a v5 capable broker running on localhost:1883.', function (done) {
|
||||
done();
|
||||
});
|
||||
}
|
||||
// Conditional test runner (only run if skipTests=false)
|
||||
function itConditional(title, test) {
|
||||
return !skipTests ? it(title, test) : it.skip(title, test);
|
||||
}
|
||||
|
||||
//#region ################### BASIC TESTS ################### #//
|
||||
|
||||
itConditional('basic send and receive tests', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: "hello",
|
||||
qos: 0
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, { done: 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 = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: '{"prop":"value1", "num":1}',
|
||||
qos: 1
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
testSendRecv({}, { datatype: "auto", topicType: "static" }, {}, options, { done: 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 = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: '{"prop":"value2", "num":2}',
|
||||
qos: 2
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
testSendRecv({}, { datatype: "utf8", topicType: "static" }, {}, options, { done: done });
|
||||
});
|
||||
itConditional('should send JSON and receive Object (json mode)', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: '{"prop":"value3", "num":3}'// send a string ...
|
||||
}
|
||||
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 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: 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
|
||||
}
|
||||
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 = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: "a b c" //send string ...
|
||||
}
|
||||
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 mode)', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(),
|
||||
payload: Buffer.from([0x78, 0x20, 0x79, 0x20, 0x7a]) // "x y z"
|
||||
}
|
||||
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 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([0xC0, 0xC1, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF]) //non valid UTF8
|
||||
}
|
||||
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) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const t = nextTopic();
|
||||
const options = {}
|
||||
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
|
||||
options.sendMsg = {
|
||||
topic: t + "/command", payload: Buffer.from('{"version":"v5"}'), qos: 1, retain: true,
|
||||
responseTopic: t + "/response",
|
||||
userProperties: { prop1: "val1" },
|
||||
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 + 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",
|
||||
qos: 1, nl: false, rap: true, rh: 1
|
||||
}
|
||||
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: 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
|
||||
}
|
||||
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;
|
||||
const options = {}
|
||||
const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
|
||||
options.sendMsg = {
|
||||
topic: nextTopic(), payload: "abc"
|
||||
}
|
||||
options.expectMsg = Object.assign({}, options.sendMsg);
|
||||
testSendRecv({ protocolVersion: 5 }, { datatype: "utf8", topicType: "dynamic" }, {}, options, hooks);
|
||||
});
|
||||
//#endregion BASIC TESTS
|
||||
|
||||
//#region ################### ADVANCED TESTS ################### #//
|
||||
itConditional('should connect via "connect" action', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null }
|
||||
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
|
||||
mqttBroker.should.have.property("autoConnect", false);
|
||||
mqttBroker.should.have.property("connecting", false);//should not attempt to connect (autoConnect:false)
|
||||
mqttIn.receive({ "action": "connect" }); //now request connect action
|
||||
return true; //handled
|
||||
}
|
||||
hooks.afterConnect = (helperNode, mqttBroker, mqttIn, mqttOut) => {
|
||||
done();//if we got here, it connected :)
|
||||
return true;
|
||||
}
|
||||
testSendRecv({ protocolVersion: 5, autoConnect: false }, { datatype: "utf8", topicType: "dynamic" }, {}, options, hooks);
|
||||
});
|
||||
itConditional('should disconnect via "disconnect" action', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const options = {}
|
||||
const hooks = { beforeLoad: null, afterLoad: null, afterConnect: null }
|
||||
hooks.beforeLoad = (flow) => { //add a status node pointed at MQTT Out node (to watch for connection status change)
|
||||
flow.push({ "id": "status.node", "type": "status", "name": "status_node", "scope": ["mqtt.out"], "wires": [["helper.node"]] });//add status node to watch mqtt_out
|
||||
}
|
||||
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
|
||||
mqttBroker.should.have.property("autoConnect", true);
|
||||
mqttBroker.should.have.property("connecting", true);//should be trying to connect (autoConnect:true)
|
||||
return true; //handled
|
||||
}
|
||||
hooks.afterConnect = (helperNode, mqttBroker, mqttIn, mqttOut) => {
|
||||
//connected - now add the "on" handler then send "disconnect" action
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("status");
|
||||
msg.status.should.have.property("text");
|
||||
msg.status.text.should.containEql('disconnect');
|
||||
done(); //it disconnected - yey!
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
})
|
||||
mqttOut.receive({ "action": "disconnect" });
|
||||
return true; //handed
|
||||
}
|
||||
testSendRecv({ protocolVersion: 5 }, null, {}, options, hooks);
|
||||
});
|
||||
itConditional('should publish birth message', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const baseTopic = nextTopic();
|
||||
const brokerOptions = {
|
||||
autoConnect: false,
|
||||
protocolVersion: 4,
|
||||
birthTopic: baseTopic + "/birth",
|
||||
birthPayload: "broker birth",
|
||||
birthQos: 2,
|
||||
}
|
||||
const expectMsg = {
|
||||
topic: brokerOptions.birthTopic,
|
||||
payload: brokerOptions.birthPayload,
|
||||
qos: brokerOptions.birthQos
|
||||
};
|
||||
const options = { };
|
||||
const hooks = { };
|
||||
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
compareMsgToExpected(msg, expectMsg);
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
})
|
||||
mqttIn.receive({ "action": "connect" }); //now request connect action
|
||||
return true; //handled
|
||||
}
|
||||
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;
|
||||
const baseTopic = nextTopic();
|
||||
const broker1Options = { id: "mqtt.broker1" }//Broker 1 - stays connected to receive the close message
|
||||
const broker2Options = { id: "mqtt.broker2", closeTopic: baseTopic + "/close", closePayload: '{"msg":"close"}', closeQos: 1, }//Broker 2 - connects to same broker but has a LWT message.
|
||||
const { flow } = buildBasicMQTTSendRecvFlow(broker1Options, { broker: broker1Options.id, topic: broker2Options.closeTopic, datatype: "json" }, { broker: broker2Options.id })
|
||||
flow.push(buildMQTTBrokerNode(broker2Options.id, broker2Options.name, BROKER_HOST, BROKER_PORT, broker2Options)); //add second broker
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
const helperNode = helper.getNode("helper.node");
|
||||
const mqttOut = helper.getNode("mqtt.out");
|
||||
const mqttBroker1 = helper.getNode("mqtt.broker1");
|
||||
const mqttBroker2 = helper.getNode("mqtt.broker2");
|
||||
waitBrokerConnect([mqttBroker1, mqttBroker2])
|
||||
.then(() => {
|
||||
//connected - add the on handler and call to disconnect
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("topic", broker2Options.closeTopic);
|
||||
msg.should.have.property('payload', JSON.parse(broker2Options.closePayload));
|
||||
msg.should.have.property('qos', broker2Options.closeQos);
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
})
|
||||
mqttOut.receive({ "action": "disconnect" });//close broker2
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
itConditional('should publish will message', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
this.timeout = 2000;
|
||||
const baseTopic = nextTopic();
|
||||
const broker1Options = { id: "mqtt.broker1" }//Broker 1 - stays connected to receive the will message
|
||||
const broker2Options = { id: "mqtt.broker2", willTopic: baseTopic + "/will", willPayload: '{"msg":"will"}', willQos: 2, }//Broker 2 - connects to same broker but has a LWT message.
|
||||
const { flow } = buildBasicMQTTSendRecvFlow(broker1Options, { broker: broker1Options.id, topic: broker2Options.willTopic, datatype: "utf8" }, { broker: broker2Options.id })
|
||||
flow.push(buildMQTTBrokerNode(broker2Options.id, broker2Options.name, BROKER_HOST, BROKER_PORT, broker2Options)); //add second broker
|
||||
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
const helperNode = helper.getNode("helper.node");
|
||||
const mqttBroker1 = helper.getNode("mqtt.broker1");
|
||||
const mqttBroker2 = helper.getNode("mqtt.broker2");
|
||||
waitBrokerConnect([mqttBroker1, mqttBroker2])
|
||||
.then(() => {
|
||||
//connected - add the on handler and call to disconnect
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("topic", broker2Options.willTopic);
|
||||
msg.should.have.property('payload', broker2Options.willPayload);
|
||||
msg.should.have.property('qos', broker2Options.willQos);
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
mqttBroker2.client.end(true); //force closure
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
itConditional('should publish will message with V5 properties', function (done) {
|
||||
if (skipTests) { return this.skip() }
|
||||
// return this.skip(); //Issue receiving v5 props on will msg. Issue raised here: https://github.com/mqttjs/MQTT.js/issues/1455
|
||||
this.timeout = 2000;
|
||||
const baseTopic = nextTopic();
|
||||
//Broker 1 - stays connected to receive the will message when broker 2 is killed
|
||||
const broker1Options = { id: "mqtt.broker1", name: "mqtt_broker1", protocolVersion: 5, datatype: "utf8" }
|
||||
//Broker 2 - connects to same broker but has a LWT message. Broker 2 gets killed shortly after connection so that the will message is sent from broker
|
||||
const broker2Options = {
|
||||
id: "mqtt.broker2", name: "mqtt_broker2", protocolVersion: 5,
|
||||
willTopic: baseTopic + "/will",
|
||||
willPayload: '{"msg":"will"}',
|
||||
willQos: 2,
|
||||
willMsg: {
|
||||
contentType: 'application/json',
|
||||
userProps: { "will": "value" },
|
||||
respTopic: baseTopic + "/resp",
|
||||
correl: Buffer.from("abc"),
|
||||
expiry: 2000,
|
||||
payloadFormatIndicator: true
|
||||
}
|
||||
}
|
||||
const expectMsg = {
|
||||
topic: broker2Options.willTopic,
|
||||
payload: broker2Options.willPayload,
|
||||
qos: broker2Options.willQos,
|
||||
contentType: broker2Options.willMsg.contentType,
|
||||
userProperties: broker2Options.willMsg.userProps,
|
||||
responseTopic: broker2Options.willMsg.respTopic,
|
||||
correlationData: broker2Options.willMsg.correl,
|
||||
messageExpiryInterval: broker2Options.willMsg.expiry,
|
||||
// payloadFormatIndicator: broker2Options.willMsg.payloadFormatIndicator,
|
||||
};
|
||||
const { flow, nodes } = buildBasicMQTTSendRecvFlow(broker1Options, { broker: broker1Options.id, topic: broker2Options.willTopic, datatype: "utf8" }, { broker: broker2Options.id })
|
||||
flow.push(buildMQTTBrokerNode(broker2Options.id, broker2Options.name, nodes.mqtt_broker1.broker, nodes.mqtt_broker1.port, broker2Options)) //add second broker with will msg set
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
const helperNode = helper.getNode("helper.node");
|
||||
const mqttBroker1 = helper.getNode("mqtt.broker1");
|
||||
const mqttBroker2 = helper.getNode("mqtt.broker2");
|
||||
waitBrokerConnect([mqttBroker1, mqttBroker2])
|
||||
.then(() => {
|
||||
//connected - add the on handler and call to disconnect
|
||||
helperNode.on("input", function (msg) {
|
||||
try {
|
||||
compareMsgToExpected(msg, expectMsg);
|
||||
done();
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
});
|
||||
mqttBroker2.client.end(true); //force closure
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
//#endregion ADVANCED TESTS
|
||||
});
|
||||
|
||||
//#region ################### HELPERS ################### #//
|
||||
|
||||
/**
|
||||
* A basic unit test that builds a flow containing 1 broker, 1 mqtt-in, one mqtt-out and a helper.
|
||||
* It performs the following steps: builds flow, loads flow, waits for connection, sends `sendMsg`,
|
||||
* waits for msg then compares `sendMsg` to `expectMsg`, and finally calls `done`
|
||||
* @param {object} brokerOptions anything that can be set in an MQTTBrokerNode (e.g. id, name, url, broker, server, port, protocolVersion, ...)
|
||||
* @param {object} inNodeOptions anything that can be set in an MQTTInNode (e.g. id, name, broker, topic, rh, nl, rap, ... )
|
||||
* @param {object} outNodeOptions anything that can be set in an MQTTOutNode (e.g. id, name, broker, ...)
|
||||
* @param {object} options an object for passing in test properties like `sendMsg` and `expectMsg`
|
||||
* @param {object} hooks an object containing hook functions...
|
||||
* * [fn] `done()` - the tests done function. If excluded, an error will be thrown upon test error
|
||||
* * [fn] `beforeLoad(flow)` - provides opportunity to adjust the flow JSON before loading into runtime
|
||||
* * [fn] `afterLoad(helperNode, mqttBroker, mqttIn, mqttOut)` - called before connection attempt
|
||||
* * [fn] `afterConnect(helperNode, mqttBroker, mqttIn, mqttOut)` - called before connection attempt
|
||||
*/
|
||||
function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hooks) {
|
||||
options = options || {};
|
||||
brokerOptions = brokerOptions || {};
|
||||
inNodeOptions = inNodeOptions || {};
|
||||
outNodeOptions = outNodeOptions || {};
|
||||
const sendMsg = options.sendMsg || {};
|
||||
sendMsg.topic = sendMsg.topic || nextTopic();
|
||||
const expectMsg = options.expectMsg || Object.assign({}, sendMsg);
|
||||
expectMsg.payload = inNodeOptions.payload === undefined ? expectMsg.payload : inNodeOptions.payload;
|
||||
if (inNodeOptions.topicType != "dynamic") {
|
||||
inNodeOptions.topic = inNodeOptions.topic || sendMsg.topic;
|
||||
}
|
||||
|
||||
const { flow, nodes } = buildBasicMQTTSendRecvFlow(brokerOptions, inNodeOptions, outNodeOptions);
|
||||
if (hooks.beforeLoad) { hooks.beforeLoad(flow) }
|
||||
helper.load(mqttNodes, flow, function () {
|
||||
try {
|
||||
const helperNode = helper.getNode("helper.node");
|
||||
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, 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(); }
|
||||
} catch (err) {
|
||||
if (hooks.done) { hooks.done(err); }
|
||||
else { throw err; }
|
||||
}
|
||||
});
|
||||
}
|
||||
waitBrokerConnect(mqttBroker)
|
||||
.then(() => {
|
||||
//finally, connected!
|
||||
if (hooks.afterConnect) {
|
||||
let handled = hooks.afterConnect(helperNode, mqttBroker, mqttIn, mqttOut);
|
||||
if (handled) { return }
|
||||
}
|
||||
if(sendMsg.topic) {
|
||||
if (mqttIn.isDynamic) {
|
||||
mqttIn.receive({ "action": "subscribe", "topic": sendMsg.topic })
|
||||
}
|
||||
mqttOut.receive(sendMsg);
|
||||
}
|
||||
})
|
||||
.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; }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a flow containing 2 parts.
|
||||
* * 1: MQTT Out node (with broker configured).
|
||||
* * 2: MQTT In node (with broker configured) --> helper node `id:helper.node`
|
||||
*/
|
||||
function buildBasicMQTTSendRecvFlow(brokerOptions, inOptions, outOptions) {
|
||||
brokerOptions = brokerOptions || {};
|
||||
brokerOptions.broker = brokerOptions.broker || BROKER_HOST;
|
||||
brokerOptions.port = brokerOptions.port || BROKER_PORT;
|
||||
brokerOptions.autoConnect = String(brokerOptions.autoConnect) == "false" ? false : true;
|
||||
const broker = buildMQTTBrokerNode(brokerOptions.id, brokerOptions.name, brokerOptions.broker, brokerOptions.port, brokerOptions);
|
||||
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, catchNode]
|
||||
}
|
||||
}
|
||||
|
||||
function buildMQTTBrokerNode(id, name, brokerHost, brokerPort, options) {
|
||||
// url,broker,port,clientid,autoConnect,usetls,usews,verifyservercert,compatmode,protocolVersion,keepalive,
|
||||
//cleansession,sessionExpiry,topicAliasMaximum,maximumPacketSize,receiveMaximum,userProperties,userPropertiesType,autoUnsubscribe
|
||||
options = options || {};
|
||||
const node = buildNode("mqtt-broker", id || "mqtt.broker", name || "mqtt_broker", options);
|
||||
node.url = options.url;
|
||||
node.broker = brokerHost || options.broker || BROKER_HOST;
|
||||
node.port = brokerPort || options.port || BROKER_PORT;
|
||||
node.clientid = options.clientid || "";
|
||||
node.cleansession = String(options.cleansession) == "false" ? false : true;
|
||||
node.autoUnsubscribe = String(options.autoUnsubscribe) == "false" ? false : true;
|
||||
node.autoConnect = String(options.autoConnect) == "false" ? false : true;
|
||||
node.sessionExpiry = options.sessionExpiry ? options.sessionExpiry : undefined;
|
||||
|
||||
if (options.birthTopic) {
|
||||
node.birthTopic = options.birthTopic;
|
||||
node.birthQos = options.birthQos || "0";
|
||||
node.birthPayload = options.birthPayload || "";
|
||||
}
|
||||
if (options.closeTopic) {
|
||||
node.closeTopic = options.closeTopic;
|
||||
node.closeQos = options.closeQos || "0";
|
||||
node.closePayload = options.closePayload || "";
|
||||
}
|
||||
if (options.willTopic) {
|
||||
node.willTopic = options.willTopic;
|
||||
node.willQos = options.willQos || "0";
|
||||
node.willPayload = options.willPayload || "";
|
||||
}
|
||||
updateNodeOptions(options, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildMQTTInNode(id, name, brokerId, topic, options, wires) {
|
||||
//{ "id": "mqtt.in", "type": "mqtt in", "name": "mqtt_in", "topic": "test/in", "qos": "2", "datatype": "auto", "broker": "mqtt.broker", "nl": false, "rap": true, "rh": 0, "inputs": 0, "wires": [["mqtt.out"]] }
|
||||
options = options || {};
|
||||
options.broker = options.broker || "mqtt.broker";
|
||||
const node = buildNode("mqtt in", id || "mqtt.in", name || "mqtt_in", options);
|
||||
node.topic = topic || "";
|
||||
node.broker = brokerId;
|
||||
node.topicType = options.topicType == "dynamic" ? "dynamic" : "static",
|
||||
node.inputs = options.topicType == "dynamic" ? 1 : 0,
|
||||
updateNodeOptions(node, options, wires);
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildMQTTOutNode(id, name, brokerId, topic, options) {
|
||||
//{ "id": "mqtt.out", "type": "mqtt out", "name": "mqtt_out", "topic": "test/out", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": brokerId, "wires": [] },
|
||||
options = options || {};
|
||||
options.broker = options.broker || "mqtt.broker";
|
||||
const node = buildNode("mqtt out", id || "mqtt.out", name || "mqtt_out", options);
|
||||
node.topic = topic || "";
|
||||
node.broker = brokerId;
|
||||
updateNodeOptions(node, options, null);
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildNode(type, id, name, options, wires) {
|
||||
//{ "id": "mqtt.in", "type": "mqtt in", "name": "mqtt_in", "topic": "test/in", "qos": "2", "datatype": "auto", "broker": "mqtt.broker", "nl": false, "rap": true, "rh": 0, "inputs": 0, "wires": [["mqtt.out"]] }
|
||||
options = options || {};
|
||||
const node = {
|
||||
"id": id || (type.replace(/[\W]/g, ".")),
|
||||
"type": type,
|
||||
"name": name || (type.replace(/[\W]/g, "_")),
|
||||
"wires": []
|
||||
}
|
||||
if (node.id.indexOf(".") == -1) { node.is += ".node" }
|
||||
updateNodeOptions(node, options, wires);
|
||||
return node;
|
||||
}
|
||||
|
||||
function updateNodeOptions(node, options, wires) {
|
||||
let keys = Object.keys(options);
|
||||
for (let index = 0; index < keys.length; index++) {
|
||||
const key = keys[index];
|
||||
const val = options[key];
|
||||
if (node[key] === undefined) {
|
||||
node[key] = val;
|
||||
}
|
||||
}
|
||||
if (wires && Array.isArray(wires)) {
|
||||
node.wires[0] = [...wires];
|
||||
}
|
||||
}
|
||||
|
||||
function compareMsgToExpected(msg, expectMsg) {
|
||||
msg.should.have.property("topic", expectMsg.topic);
|
||||
msg.should.have.property("payload", expectMsg.payload);
|
||||
if (hasProperty(expectMsg, "retain")) { msg.retain.should.eql(expectMsg.retain); }
|
||||
if (hasProperty(expectMsg, "qos")) {
|
||||
msg.qos.should.eql(expectMsg.qos);
|
||||
} else {
|
||||
msg.qos.should.eql(0);
|
||||
}
|
||||
if (hasProperty(expectMsg, "userProperties")) { msg.should.have.property("userProperties", expectMsg.userProperties); }
|
||||
if (hasProperty(expectMsg, "contentType")) { msg.should.have.property("contentType", expectMsg.contentType); }
|
||||
if (hasProperty(expectMsg, "correlationData")) { msg.should.have.property("correlationData", expectMsg.correlationData); }
|
||||
if (hasProperty(expectMsg, "responseTopic")) { msg.should.have.property("responseTopic", expectMsg.responseTopic); }
|
||||
if (hasProperty(expectMsg, "payloadFormatIndicator")) { msg.should.have.property("payloadFormatIndicator", expectMsg.payloadFormatIndicator); }
|
||||
if (hasProperty(expectMsg, "messageExpiryInterval")) { msg.should.have.property("messageExpiryInterval", expectMsg.messageExpiryInterval); }
|
||||
}
|
||||
|
||||
function waitBrokerConnect(broker, timeLimit) {
|
||||
|
||||
let waitConnected = (broker, timeLimit) => {
|
||||
const brokers = Array.isArray(broker) ? broker : [broker];
|
||||
timeLimit = timeLimit || 1000;
|
||||
return new Promise( (resolve, reject) => {
|
||||
let timer, resolved = false;
|
||||
timer = wait();
|
||||
function wait() {
|
||||
if (brokers.every(e => e.connected == true)) {
|
||||
resolved = true;
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
} else {
|
||||
timeLimit = timeLimit - 15;
|
||||
if (timeLimit <= 0) {
|
||||
if(!resolved) {
|
||||
reject("Timeout waiting broker connect")
|
||||
}
|
||||
}
|
||||
timer = setTimeout(wait, 15);
|
||||
return timer;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
return waitConnected(broker, timeLimit);
|
||||
}
|
||||
|
||||
function hasProperty(obj, propName) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, propName);
|
||||
}
|
||||
|
||||
const base_topic = "nr" + Date.now().toString() + "/";
|
||||
let topicNo = 0;
|
||||
function nextTopic(topic) {
|
||||
topicNo++;
|
||||
if (!topic) { topic = "unittest" }
|
||||
if (topic.startsWith("/")) { topic = topic.substring(1); }
|
||||
if (topic.startsWith(base_topic)) { return topic + String(topicNo) }
|
||||
return (base_topic + topic + String(topicNo));
|
||||
}
|
||||
|
||||
//#endregion HELPERS
|
||||
@@ -0,0 +1,592 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* 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 ws = require("ws");
|
||||
var should = require("should");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
var websocketNode = require("nr-test-utils").require("@node-red/nodes/core/network/22-websocket.js");
|
||||
|
||||
var sockets = [];
|
||||
|
||||
function getWsUrl(path) {
|
||||
return helper.url().replace(/http/, "ws") + path;
|
||||
}
|
||||
|
||||
function createClient(listenerid) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var node = helper.getNode(listenerid);
|
||||
var url = getWsUrl(node.path);
|
||||
var sock = new ws(url);
|
||||
sockets.push(sock);
|
||||
|
||||
sock.on("open", function() {
|
||||
resolve(sock);
|
||||
});
|
||||
|
||||
sock.on("error", function(err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
for (var i = 0; i < sockets.length; i++) {
|
||||
sockets[i].close();
|
||||
}
|
||||
sockets = [];
|
||||
}
|
||||
|
||||
function getSocket(listenerid) {
|
||||
var node = helper.getNode(listenerid);
|
||||
return node.server;
|
||||
}
|
||||
|
||||
describe('websocket Node', function() {
|
||||
|
||||
before(function(done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
closeAll();
|
||||
helper.unload();
|
||||
});
|
||||
|
||||
describe('websocket-listener', function() {
|
||||
it('should load', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket-listener", path: "/ws" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property("path", "/ws");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be server', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket-listener", path: "/ws" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property('isServer', true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle wholemsg property', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket-listener", path: "/ws2", wholemsg: "true" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property("wholemsg", false);
|
||||
helper.getNode("n2").should.have.property("wholemsg", true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should create socket', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket in", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should close socket on delete', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket-listener", path: "/ws" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("close", function(code, msg) {
|
||||
done();
|
||||
});
|
||||
helper.clearFlows();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive data', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", "hello");
|
||||
done();
|
||||
});
|
||||
sock.send("hello");
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.send('{"text":"hello"}');
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg when data not JSON', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.send('hello');
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", "hello");
|
||||
done();
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg when data not object', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", 123);
|
||||
done();
|
||||
});
|
||||
sock.send(123);
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "helper", wires: [["n3"]] },
|
||||
{ id: "n3", type: "websocket out", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("message", function(msg, flags) {
|
||||
msg.should.equal("hello");
|
||||
done();
|
||||
});
|
||||
helper.getNode("n2").send({
|
||||
payload: "hello"
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send wholemsg', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket out", server: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("message", function(msg, flags) {
|
||||
JSON.parse(msg).should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
helper.getNode("n3").send({
|
||||
text: "hello"
|
||||
});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing if no payload', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "helper", wires: [["n3"]] },
|
||||
{ id: "n3", type: "websocket out", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
setTimeout(function() {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "file";
|
||||
});
|
||||
logEvents.should.have.length(0);
|
||||
done();
|
||||
},100);
|
||||
helper.getNode("n2").send({topic: "hello"});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should echo', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "websocket out", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("message", function(msg, flags) {
|
||||
msg.should.equal("hello");
|
||||
done();
|
||||
});
|
||||
sock.send("hello");
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should echo wholemsg', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", server: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "websocket out", server: "n1" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
createClient("n1").then(function(sock) {
|
||||
sock.on("message", function(msg, flags) {
|
||||
JSON.parse(msg).should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
sock.send('{"text":"hello"}');
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should broadcast', function(done) {
|
||||
var flow = [
|
||||
{ id: "n1", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket out", server: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
Promise.all([createClient("n1"), createClient("n1")]).then(function(socks) {
|
||||
var promises = [
|
||||
new Promise((resolve,reject) => {
|
||||
socks[0].on("message", function(msg, flags) {
|
||||
try {
|
||||
msg.should.equal("hello");
|
||||
resolve();
|
||||
} catch(err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}),
|
||||
new Promise((resolve,reject) => {
|
||||
socks[1].on("message", function(msg, flags) {
|
||||
try {
|
||||
msg.should.equal("hello");
|
||||
resolve();
|
||||
} catch(err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
];
|
||||
helper.getNode("n3").send({
|
||||
payload: "hello"
|
||||
});
|
||||
return Promise.all(promises).then(() => {done()});
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('websocket-client', function() {
|
||||
it('should load', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property('path', getWsUrl("/ws"));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be server', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property('isServer', false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle wholemsg property', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property("wholemsg", false);
|
||||
helper.getNode("n2").should.have.property("wholemsg", true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle protocol property', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), subprotocol: "testprotocol1, testprotocol2" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property("subprotocol", []);
|
||||
helper.getNode("n2").should.have.property("subprotocol", ["testprotocol1","testprotocol2"]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should connect to server', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws") }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('should initiate with subprotocol', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), subprotocol: "testprotocol" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function (sock) {
|
||||
sock.should.have.property("protocol", "testprotocol")
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should close on delete', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws") }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.on('close', function() {
|
||||
done();
|
||||
});
|
||||
helper.getNode("n2").close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive data', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket in", client: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.send('hello');
|
||||
});
|
||||
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", "hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg data ', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", client: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.send('{"text":"hello"}');
|
||||
});
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive wholemsg when data not JSON', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket in", client: "n1", wires: [["n3"]] },
|
||||
{ id: "n3", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.send('hello');
|
||||
});
|
||||
helper.getNode("n3").on("input", function(msg) {
|
||||
msg.should.have.property("payload", "hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket out", client: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.on('message', function(msg) {
|
||||
msg.should.equal("hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
getSocket("n1").on("open", function() {
|
||||
helper.getNode("n3").send({
|
||||
payload: "hello"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send buffer', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket out", client: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.on('message', function(msg) {
|
||||
Buffer.isBuffer(msg).should.be.true();
|
||||
msg.should.have.length(5);
|
||||
done();
|
||||
});
|
||||
});
|
||||
getSocket("n1").on("open", function() {
|
||||
helper.getNode("n3").send({
|
||||
payload: Buffer.from("hello")
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should send wholemsg', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" },
|
||||
{ id: "n2", type: "websocket out", client: "n1" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function(sock) {
|
||||
sock.on('message', function(msg) {
|
||||
JSON.parse(msg).should.have.property("text", "hello");
|
||||
done();
|
||||
});
|
||||
});
|
||||
getSocket("n1").on('open', function(){
|
||||
helper.getNode("n3").send({
|
||||
text: "hello"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT feedback more than once', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws", wholemsg: "true" },
|
||||
{ id: "client", type: "websocket-client", path: getWsUrl("/ws"), wholemsg: "true" },
|
||||
{ id: "n1", type: "websocket in", client: "client", wires: [["n2", "output"]] },
|
||||
{ id: "n2", type: "websocket out", server: "server" },
|
||||
{ id: "n3", type: "helper", wires: [["n2"]] },
|
||||
{ id: "output", type: "helper" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('client').on('open', function() {
|
||||
helper.getNode("n3").send({
|
||||
payload: "ping"
|
||||
});
|
||||
});
|
||||
var acc = 0;
|
||||
helper.getNode("output").on("input", function(msg) {
|
||||
acc = acc + 1;
|
||||
});
|
||||
setTimeout( function() {
|
||||
acc.should.equal(1);
|
||||
helper.clearFlows();
|
||||
done();
|
||||
}, 250);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('websocket in node', function() {
|
||||
it('should report error if no server config', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket in", mode: "server" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "websocket in";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.toString().should.startWith("websocket.errors.missing-conf");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('websocket out node', function() {
|
||||
it('should report error if no server config', function(done) {
|
||||
var flow = [{ id: "n1", type: "websocket out", mode: "server" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "websocket out";
|
||||
});
|
||||
//console.log(logEvents);
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.toString().should.startWith("websocket.errors.missing-conf");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* 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 net = require("net");
|
||||
var should = require("should");
|
||||
var stoppable = require('stoppable');
|
||||
var helper = require("node-red-node-test-helper");
|
||||
|
||||
var tcpinNode = require("nr-test-utils").require("@node-red/nodes/core/network/31-tcpin.js");
|
||||
|
||||
|
||||
describe('TCP in Node', function() {
|
||||
var port = 9200;
|
||||
var server = undefined;
|
||||
var server_port = 9300;
|
||||
var reply_data = undefined;
|
||||
|
||||
beforeEach(function(done) {
|
||||
startServer(done);
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
helper.unload();
|
||||
stopServer(done);
|
||||
});
|
||||
|
||||
function sendArray(sock, array) {
|
||||
if(array.length > 0) {
|
||||
sock.write(array[0], function() {
|
||||
sendArray(sock, array.slice(1));
|
||||
});
|
||||
}
|
||||
else {
|
||||
sock.end();
|
||||
}
|
||||
}
|
||||
|
||||
function startServer(done) {
|
||||
server_port += 1;
|
||||
server = stoppable(net.createServer(function(c) {
|
||||
sendArray(c, reply_data);
|
||||
})).listen(server_port, "localhost", function(err) {
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
|
||||
function stopServer(done) {
|
||||
server.stop(done);
|
||||
}
|
||||
|
||||
function send(wdata) {
|
||||
var opt = {port:port, host:"localhost"};
|
||||
var client = net.createConnection(opt, function() {
|
||||
client.write(wdata[0], function() {
|
||||
client.end();
|
||||
if(wdata.length > 1) {
|
||||
send(wdata.slice(1));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function eql(v0, v1) {
|
||||
return((v0 === v1) || ((typeof v0) === 'object' && v0.equals(v1)));
|
||||
}
|
||||
|
||||
function testTCP(flow, wdata, rdata, is_server, done) {
|
||||
if(is_server) {
|
||||
reply_data = wdata;
|
||||
}
|
||||
helper.load(tcpinNode, flow, function() {
|
||||
var n2 = helper.getNode("n2");
|
||||
var rcount = 0;
|
||||
n2.on("input", function(msg) {
|
||||
if(eql(msg.payload, rdata[rcount])) {
|
||||
rcount++;
|
||||
}
|
||||
else {
|
||||
should.fail();
|
||||
}
|
||||
if(rcount === rdata.length) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
if(!is_server) {
|
||||
send(wdata);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testTCP0(flow, wdata, rdata, done) {
|
||||
testTCP(flow, wdata, rdata, false, done);
|
||||
}
|
||||
|
||||
function testTCP1(flow, wdata, rdata, done) {
|
||||
testTCP(flow, wdata, rdata, true, done);
|
||||
}
|
||||
|
||||
it('should recv data (Stream/Buffer)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"stream", datatype:"buffer", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP0(flow, ["foo"], [Buffer("foo")], done);
|
||||
});
|
||||
|
||||
it('should recv data (Stream/String/Delimiter:\\n)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"stream", datatype:"utf8", newline:"\n", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP0(flow, ["foo\nbar"], ["foo", "bar"], done);
|
||||
});
|
||||
|
||||
it('should recv data (Stream/String/Delimiter:o\\n)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"stream", datatype:"utf8", newline:"o\n", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
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"}];
|
||||
testTCP0(flow, ["foo\nbar"], ["foo\nbar"], done);
|
||||
});
|
||||
|
||||
it('should recv data (Stream/Base64)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"stream", datatype:"base64", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP0(flow, ["foo"], [Buffer("foo").toString('base64')], done);
|
||||
});
|
||||
|
||||
it('should recv data (Single/Buffer)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"single", datatype:"buffer", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP0(flow, ["foo"], [Buffer("foo")], done);
|
||||
});
|
||||
|
||||
it('should recv data (Single/String)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"single", datatype:"utf8", newline:"\n", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP0(flow, ["foo\nbar\nbaz"], ["foo\nbar\nbaz"], done);
|
||||
});
|
||||
|
||||
it('should recv data (Stream/Base64)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"single", datatype:"base64", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP0(flow, ["foo"], [Buffer("foo").toString('base64')], done);
|
||||
});
|
||||
|
||||
it('should recv multiple data (Stream/Buffer)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"stream", datatype:"buffer", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP0(flow, ["foo", "bar"], [Buffer("foo"), Buffer("bar")], done);
|
||||
});
|
||||
|
||||
it('should recv multiple data (Stream/String/Delimiter:\\n)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"stream", datatype:"utf8", newline:"\n", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP0(flow, ["foo", "bar\nbaz"], ["foo", "bar", "baz"], done);
|
||||
});
|
||||
|
||||
it('should recv multiple 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"}];
|
||||
testTCP0(flow, ["foo", "bar\nbaz"], ["foo", "bar\nbaz"], done);
|
||||
});
|
||||
|
||||
it('should recv multiple data (Stream/Base64)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"server", host:"localhost", port:port, datamode:"stream", datatype:"base64", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
var wdata = ["foo", "bar"];
|
||||
var rdata = wdata.map(function(x) {
|
||||
return Buffer(x).toString('base64');
|
||||
});
|
||||
testTCP0(flow, wdata, rdata, done);
|
||||
});
|
||||
|
||||
it('should connect & recv data (Stream/Buffer)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"client", host:"localhost", port:server_port, datamode:"stream", datatype:"buffer", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP1(flow, ["foo"], [Buffer("foo")], done);
|
||||
});
|
||||
|
||||
it('should connect & recv data (Stream/String/Delimiter:\\n)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"client", host:"localhost", port:server_port, datamode:"stream", datatype:"utf8", newline:"\n", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP1(flow, ["foo\nbar"], ["foo", "bar"], done);
|
||||
});
|
||||
|
||||
it('should connect & recv data (Stream/String/No delimiter)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"client", host:"localhost", port:server_port, datamode:"stream", datatype:"utf8", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP1(flow, ["foo\nbar"], ["foo\nbar"], done);
|
||||
});
|
||||
|
||||
it('should connect & recv data (Stream/Base64)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"client", host:"localhost", port:server_port, datamode:"stream", datatype:"base64", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP1(flow, ["foo"], [Buffer("foo").toString('base64')], done);
|
||||
});
|
||||
|
||||
it('should connect & recv data (Single/Buffer)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"client", host:"localhost", port:server_port, datamode:"single", datatype:"buffer", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP1(flow, ["foo"], [Buffer("foo")], done);
|
||||
});
|
||||
|
||||
it('should connect & recv data (Single/String)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"client", host:"localhost", port:server_port, datamode:"single", datatype:"utf8", newline:"\n", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP1(flow, ["foo\nbar\nbaz"], ["foo\nbar\nbaz"], done);
|
||||
});
|
||||
|
||||
it('should connect & recv data (Stream/Base64)', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp in", server:"client", host:"localhost", port:server_port, datamode:"single", datatype:"base64", newline:"", topic:"", base64:false, wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP1(flow, ["foo"], [Buffer("foo").toString('base64')], done);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* 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 net = require("net");
|
||||
var should = require("should");
|
||||
var stoppable = require('stoppable');
|
||||
var helper = require("node-red-node-test-helper");
|
||||
var tcpinNode = require("nr-test-utils").require("@node-red/nodes/core/network/31-tcpin.js");
|
||||
var RED = require("nr-test-utils").require("node-red/lib/red.js");
|
||||
|
||||
|
||||
describe('TCP Request Node', function() {
|
||||
var server = undefined;
|
||||
var port = 9000;
|
||||
|
||||
function startServer(done) {
|
||||
port += 1;
|
||||
server = stoppable(net.createServer(function(c) {
|
||||
c.on('data', function(data) {
|
||||
var rdata = "ACK:"+data.toString();
|
||||
c.write(rdata);
|
||||
});
|
||||
c.on('error', function(err) {
|
||||
startServer(done);
|
||||
});
|
||||
})).listen(port, "127.0.0.1", function(err) {
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
before(function(done) {
|
||||
startServer(done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
server.stop(done);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.unload();
|
||||
});
|
||||
|
||||
function testTCP(flow, val0, val1, done) {
|
||||
helper.load(tcpinNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
if (typeof val1 === 'object') {
|
||||
msg.should.have.properties(Object.assign({}, val1, {payload: Buffer.from(val1.payload)}));
|
||||
} else {
|
||||
msg.should.have.property('payload', Buffer.from(val1));
|
||||
}
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
if((typeof val0) === 'object') {
|
||||
n1.receive(val0);
|
||||
} else {
|
||||
n1.receive({payload:val0});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testTCPMany(flow, values, result, done) {
|
||||
helper.load(tcpinNode, flow, () => {
|
||||
const n1 = helper.getNode("n1");
|
||||
const n2 = helper.getNode("n2");
|
||||
n2.on("input", msg => {
|
||||
try {
|
||||
if (typeof result === 'object') {
|
||||
if (flow[0].ret === "string") {
|
||||
msg.should.have.properties(Object.assign({}, result, {payload: result.payload}));
|
||||
} else {
|
||||
msg.should.have.properties(Object.assign({}, result, {payload: Buffer.from(result.payload)}));
|
||||
}
|
||||
} else {
|
||||
if (flow[0].ret === "string") {
|
||||
msg.should.have.property('payload', result);
|
||||
} else {
|
||||
msg.should.have.property('payload', Buffer.from(result));
|
||||
}
|
||||
}
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
values.forEach(value => {
|
||||
n1.receive(typeof value === 'object' ? value : {payload: value});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('single message', function () {
|
||||
it('should send & recv data', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should retain complete message', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data when specified character received', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"char", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo0bar0',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo0',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data after fixed number of chars received', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"count", splitc: "7", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo bar',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", splitc: "5", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: 'foo',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data to/from server:port from msg', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"", port:"", out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCP(flow, {
|
||||
payload: "foo",
|
||||
host: "localhost",
|
||||
port: port
|
||||
}, {
|
||||
payload: "ACK:foo",
|
||||
host: 'localhost',
|
||||
port: port
|
||||
}, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('many messages', function () {
|
||||
it('should send & recv data', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: 'f',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'o',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'o',
|
||||
topic: 'bar'
|
||||
}], {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data when specified character received', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"char", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo0",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "bar0",
|
||||
topic: 'bar'
|
||||
}], {
|
||||
payload: "ACK:foo0",
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data after fixed number of chars received', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"count", splitc: "7", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "fo",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "ob",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "ar",
|
||||
topic: 'bar'
|
||||
}], {
|
||||
payload: "ACK:foo",
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", splitc: "5", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "bar",
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: "baz",
|
||||
topic: 'bar'
|
||||
}], {
|
||||
payload: "ACK:foobarbaz",
|
||||
topic: 'bar'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection, and not split return strings', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", ret:"string", newline:"", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo",
|
||||
topic: 'boo'
|
||||
}, {
|
||||
payload: "bar<A>\nfoo",
|
||||
topic: 'boo'
|
||||
}], {
|
||||
payload: "ACK:foobar<A>\nfoo",
|
||||
topic: 'boo'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection, and split return strings', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", ret:"string", newline:"<A>\\n", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo",
|
||||
topic: 'boo'
|
||||
}, {
|
||||
payload: "bar<A>\nfoo",
|
||||
topic: 'boo'
|
||||
}], {
|
||||
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);
|
||||
});
|
||||
|
||||
it('should send & recv data to/from server:port from msg', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"", port:"", out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [
|
||||
{
|
||||
payload: "f",
|
||||
host: "localhost",
|
||||
port: port
|
||||
},
|
||||
{
|
||||
payload: "o",
|
||||
host: "localhost",
|
||||
port: port
|
||||
},
|
||||
{
|
||||
payload: "o",
|
||||
host: "localhost",
|
||||
port: port
|
||||
}
|
||||
], {
|
||||
payload: "ACK:foo",
|
||||
host: 'localhost',
|
||||
port: port
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should limit the queue size', function (done) {
|
||||
RED.settings.tcpMsgQueueSize = 10;
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", splitc: "5", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
// create one more msg than is allowed
|
||||
const msgs = new Array(RED.settings.tcpMsgQueueSize + 1).fill('x');
|
||||
const expected = msgs.slice(0, -1);
|
||||
testTCPMany(flow, msgs, "ACK:" + expected.join(''), done);
|
||||
});
|
||||
|
||||
it('should only retain the latest message', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: 'f',
|
||||
topic: 'bar'
|
||||
}, {
|
||||
payload: 'o',
|
||||
topic: 'baz'
|
||||
}, {
|
||||
payload: 'o',
|
||||
topic: 'quux'
|
||||
}], {
|
||||
payload: 'ACK:foo',
|
||||
topic: 'quux'
|
||||
}, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* 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 dgram = require("dgram");
|
||||
var should = require("should");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
var udpNode = require("nr-test-utils").require("@node-red/nodes/core/network/32-udp.js");
|
||||
|
||||
|
||||
describe('UDP in Node', function() {
|
||||
var port = 9100;
|
||||
|
||||
before(function(done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.unload();
|
||||
});
|
||||
|
||||
function sendIPv4(msg) {
|
||||
var sock = dgram.createSocket('udp4');
|
||||
sock.send(msg, 0, msg.length, port, "127.0.0.1", function(msg) {
|
||||
sock.close();
|
||||
});
|
||||
}
|
||||
|
||||
function checkRecv(dt, proto, val0, val1, done) {
|
||||
var flow = [{id:"n1", type:"udp in",
|
||||
group: "", multicast:false,
|
||||
port:port, ipv:proto,
|
||||
datatype: dt, iface: "",
|
||||
wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
helper.load(udpNode, flow, function() {
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
var ip = ((proto === 'udp6') ? '::ffff:':'') +'127.0.0.1';
|
||||
msg.should.have.property('ip', ip);
|
||||
msg.should.have.property('port');
|
||||
msg.should.have.property('payload');
|
||||
msg.payload.should.deepEqual(val1);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
sendIPv4(val0);
|
||||
});
|
||||
}
|
||||
|
||||
it('should recv IPv4 data (Buffer)', function(done) {
|
||||
checkRecv('buffer', 'udp4', 'hello', Buffer('hello'), done);
|
||||
});
|
||||
|
||||
it('should recv IPv4 data (String)', function(done) {
|
||||
checkRecv('utf8', 'udp4', 'hello', 'hello', done);
|
||||
});
|
||||
|
||||
it('should recv IPv4 data (base64)', function(done) {
|
||||
checkRecv('base64', 'udp4', 'hello', Buffer('hello').toString('base64'), done);
|
||||
});
|
||||
|
||||
it('should recv IPv6 data (Buffer)', function(done) {
|
||||
checkRecv('buffer', 'udp6', 'hello', Buffer('hello'), done);
|
||||
});
|
||||
|
||||
it('should recv IPv6 data (String)', function(done) {
|
||||
checkRecv('utf8', 'udp6', 'hello', 'hello', done);
|
||||
});
|
||||
|
||||
it('should recv IPv6 data (base64)', function(done) {
|
||||
checkRecv('base64', 'udp6', 'hello', Buffer('hello').toString('base64'), done);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* 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 dgram = require("dgram");
|
||||
var should = require("should");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
var udpNode = require("nr-test-utils").require("@node-red/nodes/core/network/32-udp.js");
|
||||
|
||||
|
||||
describe('UDP out Node', function() {
|
||||
var port = 9200;
|
||||
|
||||
before(function(done) {
|
||||
helper.startServer(done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
helper.stopServer(done);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.unload();
|
||||
});
|
||||
|
||||
function recvData(data, done) {
|
||||
var sock = dgram.createSocket('udp4');
|
||||
sock.on('message', function(msg, rinfo) {
|
||||
sock.close(done);
|
||||
msg.should.deepEqual(data);
|
||||
});
|
||||
sock.bind(port, '127.0.0.1');
|
||||
port++;
|
||||
}
|
||||
|
||||
function checkSend(proto, val0, val1, decode, dest_in_msg, done) {
|
||||
var dst_ip = dest_in_msg ? undefined : "127.0.0.1";
|
||||
var dst_port = dest_in_msg ? undefined : port;
|
||||
var flow = [{id:"n1", type:"udp out",
|
||||
addr:dst_ip, port:dst_port, iface: "",
|
||||
ipv:proto, outport: "",
|
||||
base64:decode, multicast:false,
|
||||
wires:[] }];
|
||||
helper.load(udpNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var msg = {};
|
||||
if (decode) {
|
||||
msg.payload = Buffer.from("hello").toString('base64');
|
||||
}
|
||||
else {
|
||||
msg.payload = "hello";
|
||||
}
|
||||
if (dest_in_msg) {
|
||||
msg.ip = "127.0.0.1";
|
||||
msg.port = port;
|
||||
}
|
||||
recvData(val1, done);
|
||||
setTimeout(function() {
|
||||
n1.receive(msg);
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
it('should send IPv4 data', function(done) {
|
||||
checkSend('udp4', 'hello', Buffer.from('hello'), false, false, done);
|
||||
});
|
||||
|
||||
it('should send IPv4 data (base64)', function(done) {
|
||||
checkSend('udp4', 'hello', Buffer.from('hello'), true, false, done);
|
||||
});
|
||||
|
||||
it('should send IPv4 data with dest from msg', function(done) {
|
||||
checkSend('udp4', 'hello', Buffer.from('hello'), false, true, done);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,712 @@
|
||||
const should = require("should");
|
||||
|
||||
/*****
|
||||
|
||||
Issues with the current *_proxy implementation in Node-RED:
|
||||
* no_proxy should not be case-sensitive
|
||||
* i.e. if no_proxy contains "example.com", then "example.com" and "EXAMPLE.COM" should both be excluded
|
||||
* no_proxy with protocols that have a default port are not considered
|
||||
* i.e. if no_proxy contains "example.com:443", then "https://example.com" should be excluded
|
||||
* i.e. if no_proxy contains "example.com:80", then "http://example.com" should be excluded
|
||||
* i.e. if no_proxy contains "example.com:1880", then "mqtt://example.com" should be excluded
|
||||
* Does not consider NPM proxy configuration at all
|
||||
* i.e. if npm_config_proxy is set, then it should be used
|
||||
* i.e. if npm_config_https_proxy is set, then it should be used
|
||||
* i.e. if npm_config_http_proxy is set, then it should be used
|
||||
* i.e. if npm_config_no_proxy is set, then it should be used
|
||||
* Doesn't consider https_proxy or HTTPS_PROXY
|
||||
* i.e. if https_proxy is set, then it should be used
|
||||
* i.e. if HTTPS_PROXY is set, then it should be used
|
||||
* i.e. incorrectly uses HTTP_PROXY 'http://http-proxy' when the url is 'https://example'
|
||||
* Incorrectly prioritises HTTP_PROXY over http_proxy. HTTP_PROXY is not always supported or recommended
|
||||
* i.e. if HTTP_PROXY and http_proxy are both set, then http_proxy should be used
|
||||
* Use lowercase form. HTTP_PROXY is not always supported or recommended
|
||||
* doesn't consider all_proxy or ALL_PROXY
|
||||
* i.e. if all_proxy is set, then it should be used
|
||||
* i.e. if ALL_PROXY is set, then it should be used
|
||||
*
|
||||
*
|
||||
This implementation is based on the following sources:
|
||||
* https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/ (GitLab)
|
||||
* https://www.npmjs.com/package/proxy-from-env (MIT License)
|
||||
|
||||
This implementation proposal follows the following rules:
|
||||
* Support the following PROTOCOL_proxys
|
||||
* i.e. http_proxy, https_proxy, mqtt_proxy, ws_proxy, wss_proxy, mqtt_proxy, mqtts_proxy
|
||||
* Support all_proxy
|
||||
* i.e. if all_proxy is set, then all URLs will be proxied
|
||||
* Use comma-separated hostname[:port] values for no_proxy.
|
||||
* no_proxy should contain a comma-separated list of domain extensions proxy should not be used for
|
||||
* Each value may include optional whitespace.
|
||||
* port is optional and is inferred if the protocol has a default port (supports http, https, mqtt, ws, wss, mqtt, mqtts)
|
||||
* Use * to match all hosts
|
||||
* Support .example (host suffix)
|
||||
* Support sub.example (host sub domain)
|
||||
* Upper case forms of *_PROXY are supported but not recommended
|
||||
* Lower case forms of *_proxy will take precedence over upper case forms
|
||||
* Does not perform DNS lookups or use regular expressions
|
||||
* Does not perform validation on the *_proxy urls
|
||||
* Does not support CIDR block matching
|
||||
* Support IPv6 matching
|
||||
******/
|
||||
|
||||
/* eslint max-statements:0 */
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const { getProxyForUrl } = require("nr-test-utils").require('@node-red/nodes/core/network/lib/proxyHelper')
|
||||
|
||||
/**
|
||||
* Defines a test case that checks whether getProxyForUrl(input) === expected.
|
||||
* @param {object} env - The environment variables to use for the test
|
||||
* @param {*} expected - The expected result
|
||||
* @param {*} input - The input to test
|
||||
* @param {import('../../../../../packages/node_modules/@node-red/nodes/core/network/lib/proxyHelper').ProxyOptions} [options] - The options to use for getProxyForUrl
|
||||
* @param {string} [testName] - The name of the test (auto computed if not provided)
|
||||
*/
|
||||
function testProxyUrl(env, expected, input, options, testName) {
|
||||
assert(typeof env === 'object' && env !== null);
|
||||
// Copy object to make sure that the in param does not get modified between
|
||||
// the call of this function and the use of it below.
|
||||
env = JSON.parse(JSON.stringify(env));
|
||||
|
||||
const title = testName || 'Proxy for URL ' + JSON.stringify(input) + ' === ' + JSON.stringify(expected);
|
||||
|
||||
// Save call stack for later use.
|
||||
let stack = {};
|
||||
Error.captureStackTrace(stack, testProxyUrl);
|
||||
// Only use the last stack frame because that shows where this function is
|
||||
// called, and that is sufficient for our purpose. No need to flood the logs
|
||||
// with an uninteresting stack trace.
|
||||
stack = stack.stack.split('\n', 2)[1];
|
||||
|
||||
it(title, function () {
|
||||
let actual;
|
||||
// runWithEnv(env, function () {
|
||||
// actual = getProxyForUrl(input, options);
|
||||
// });
|
||||
options = options || {};
|
||||
options.env = options.env || env || process.env;
|
||||
actual = getProxyForUrl(input, options);
|
||||
if (expected === actual) {
|
||||
return; // Good!
|
||||
}
|
||||
try {
|
||||
assert.strictEqual(expected, actual); // Create a formatted error message.
|
||||
// Should not happen because previously we determined expected !== actual.
|
||||
throw new Error('assert.strictEqual passed. This is impossible!');
|
||||
} catch (e) {
|
||||
// Use the original stack trace, so we can see a helpful line number.
|
||||
e.stack = e.message + stack;
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('Proxy Helper', function () {
|
||||
describe('No proxy variables', function () {
|
||||
const env = {};
|
||||
testProxyUrl(env, '', 'http://example.com');
|
||||
testProxyUrl(env, '', 'https://example.com');
|
||||
testProxyUrl(env, '', 'ftp://example.com');
|
||||
testProxyUrl(env, '', 'ws://example.com');
|
||||
testProxyUrl(env, '', 'wss://example.com');
|
||||
testProxyUrl(env, '', 'mqtt://example.com');
|
||||
testProxyUrl(env, '', 'mqtts://example.com');
|
||||
});
|
||||
|
||||
describe('Invalid URLs', function () {
|
||||
const env = {};
|
||||
env.ALL_PROXY = 'http://unexpected.proxy';
|
||||
testProxyUrl(env, '', 'bogus');
|
||||
testProxyUrl(env, '', '//example.com');
|
||||
testProxyUrl(env, '', '://example.com');
|
||||
testProxyUrl(env, '', '://');
|
||||
testProxyUrl(env, '', '/path');
|
||||
testProxyUrl(env, '', '');
|
||||
testProxyUrl(env, '', 'ws:');
|
||||
testProxyUrl(env, '', 'wss:');
|
||||
testProxyUrl(env, '', 'mqtt:');
|
||||
testProxyUrl(env, '', 'mqtts:');
|
||||
testProxyUrl(env, '', 'http:');
|
||||
testProxyUrl(env, '', 'http:/');
|
||||
testProxyUrl(env, '', 'http://');
|
||||
testProxyUrl(env, '', 'prototype://');
|
||||
testProxyUrl(env, '', 'hasOwnProperty://');
|
||||
testProxyUrl(env, '', '__proto__://');
|
||||
testProxyUrl(env, '', undefined);
|
||||
testProxyUrl(env, '', null);
|
||||
testProxyUrl(env, '', {});
|
||||
testProxyUrl(env, '', { host: 'x', protocol: 1 });
|
||||
testProxyUrl(env, '', { host: 1, protocol: 'x' });
|
||||
});
|
||||
describe('Proxy options', function () {
|
||||
describe('lowerCaseOnly:true should prevent *_PROXY being returned', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://upper-case-proxy';
|
||||
env.HTTPS_PROXY = 'https://upper-case-proxy';
|
||||
env.http_proxy = '';
|
||||
env.https_proxy = '';
|
||||
env.no_proxy = '';
|
||||
testProxyUrl(env, '', 'http://example', { lowerCaseOnly: true }, 'returns empty string because `lowerCaseOnly` is set and http_proxy is not set');
|
||||
testProxyUrl(env, '', 'https://example', { lowerCaseOnly: true }, 'returns empty string because `lowerCaseOnly` is set and https_proxy is not set');
|
||||
testProxyUrl(env, 'http://upper-case-proxy', 'http://example', null, 'returns HTTP_PROXY because lowerCaseOnly is false by default');
|
||||
testProxyUrl(env, 'https://upper-case-proxy', 'https://example', null, 'returns HTTPS_PROXY because lowerCaseOnly is false by default');
|
||||
});
|
||||
|
||||
describe('favourUpperCase:false should cause *_PROXY to being used before *_proxy', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://upper-case-proxy';
|
||||
env.http_proxy = 'http://lower-case-proxy';
|
||||
testProxyUrl(env, 'http://upper-case-proxy', 'http://example', { favourUpperCase: true }, 'returns HTTP_PROXY by due to `favourUpperCase`');
|
||||
testProxyUrl(env, 'http://lower-case-proxy', 'http://example', null, 'returns http_proxy by as it takes precedence by default');
|
||||
});
|
||||
|
||||
describe('includeNpm:false should not return npm_config_*_proxy env vars', function () {
|
||||
const env = {};
|
||||
env.npm_config_http_proxy = 'http://npm-proxy';
|
||||
env.npm_config_https_proxy = 'https://npm-proxy';
|
||||
testProxyUrl(env, '', 'http://example', { excludeNpm: true });
|
||||
testProxyUrl(env, 'http://npm-proxy', 'http://example'); // lowercase takes precedence by default
|
||||
testProxyUrl(env, 'https://npm-proxy', 'https://example');
|
||||
});
|
||||
|
||||
describe('When legacy mode is true, should process urls proxy in node-red <= v3.1 compatibility mode', function () {
|
||||
const env = {};
|
||||
// legacy mode does not consider npm_config_*_proxy
|
||||
env.npm_config_http_proxy = 'http://npm-proxy';
|
||||
testProxyUrl(env, '', 'http://example/1', { mode: 'legacy' });
|
||||
testProxyUrl(env, 'http://npm-proxy', 'http://example/1');
|
||||
|
||||
// legacy mode does not consider all_proxy
|
||||
env.all_proxy = 'http://all-proxy';
|
||||
testProxyUrl(env, '', 'http://example/2', { mode: 'legacy' }); // returns empty string in "legacy" mode
|
||||
|
||||
// legacy mode does not consider *_proxy
|
||||
env.npm_config_http_proxy = null;
|
||||
env.http_proxy = 'http://http-proxy';
|
||||
env.no_proxy = 'example';
|
||||
testProxyUrl(env, '', 'http://example/3a', { mode: 'legacy' });
|
||||
|
||||
// legacy mode does not consider protocol_proxy for https urls and uses http_proxy instead
|
||||
env.https_proxy = 'https://https-proxy';
|
||||
env.no_proxy = '';
|
||||
testProxyUrl(env, 'http://http-proxy', 'https://example/4', { mode: 'legacy' }); // returns http_proxy instead of https_proxy
|
||||
|
||||
// legacy mode favours UPPER_CASE over lower_case
|
||||
env.HTTP_PROXY = 'http://http-proxy-upper';
|
||||
env.http_proxy = 'http://http-proxy';
|
||||
env.no_proxy = '';
|
||||
testProxyUrl(env, 'http://http-proxy-upper', 'http://example/5', { mode: 'legacy' }, 'returns HTTP_PROXY "http://http-proxy-upper" because mode is "legacy"');
|
||||
|
||||
// no_proxy with protocols that have a default port are not considered
|
||||
// * i.e. if no_proxy contains "example.com:443", then "https://example.com" should be excluded
|
||||
// * i.e. if no_proxy contains "example.com:80", then "http://example.com" should be excluded
|
||||
// * i.e. if no_proxy contains "example.com:1880", then "mqtt://example.com" should be excluded
|
||||
env.HTTP_PROXY = 'http://http-proxy';
|
||||
env.http_proxy = 'http://http-proxy';
|
||||
env.no_proxy = 'example.com:80';
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://example.com', { mode: 'legacy' }, 'incorrectly returns http_proxy for "http://example.com" when mode is "legacy"');
|
||||
testProxyUrl(env, '', 'http://example.com:80', { mode: 'legacy' }); // works as expected
|
||||
testProxyUrl(env, '', 'http://example.com:8080', { mode: 'legacy' }, 'incorrectly returns http_proxy for "http://example.com:8080" when mode is "legacy"');
|
||||
|
||||
// legacy mode does not correctly process no_proxy with protocols that have a default port
|
||||
env.HTTP_PROXY = 'http://http-proxy';
|
||||
env.http_proxy = 'http://http-proxy';
|
||||
env.no_proxy = 'example.com:80';
|
||||
testProxyUrl(env, '', 'http://example.com:80', { mode: 'legacy' }); // works as expected
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://example.com', { mode: 'legacy' }, 'incorrectly returns http_proxy for "http://example.com" when no_proxy is "example.com:80" and mode is "legacy"');
|
||||
|
||||
env.HTTP_PROXY = 'http://http-proxy';
|
||||
env.NO_PROXY = '[::1],[::2]:80,10.0.0.1,10.0.0.2:80';
|
||||
testProxyUrl(env, '', 'http://[::1]/', { mode: 'legacy' });
|
||||
testProxyUrl(env, '', 'http://[::1]:80/', { mode: 'legacy' });
|
||||
testProxyUrl(env, '', 'http://[::1]:1337/', { mode: 'legacy' });
|
||||
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://[::2]/', { mode: 'legacy' }); // http://[::2]/ is essentially the same as http://[::2]:80, this should NOT be proxied
|
||||
testProxyUrl(env, '', 'http://[::2]:80/', { mode: 'legacy' });
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://[::2]:1337/', { mode: 'legacy' });
|
||||
|
||||
testProxyUrl(env, '', 'http://10.0.0.1/', { mode: 'legacy' });
|
||||
testProxyUrl(env, '', 'http://10.0.0.1:80/', { mode: 'legacy' });
|
||||
testProxyUrl(env, '', 'http://10.0.0.1:1337/', { mode: 'legacy' });
|
||||
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://10.0.0.2/', { mode: 'legacy' }); // http://10.0.0.2 is essentially the same as http://10.0.0.2:80, this should NOT be proxied
|
||||
testProxyUrl(env, '', 'http://10.0.0.2:80/', { mode: 'legacy' });
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://10.0.0.2:1337/', { mode: 'legacy' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('http_proxy and HTTP_PROXY', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://http-proxy';
|
||||
|
||||
testProxyUrl(env, '', 'https://example');
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://example');
|
||||
testProxyUrl(env, 'http://http-proxy', new URL('http://example'));
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
env.http_proxy = 'http://priority';
|
||||
testProxyUrl(env, 'http://priority', 'http://example');
|
||||
});
|
||||
|
||||
describe('http_proxy with nonsense value', function () {
|
||||
const env = {};
|
||||
// Crazy values should be passed as-is. It is the responsibility of the
|
||||
// one who launches the application that the value makes sense.
|
||||
env.HTTP_PROXY = 'Crazy \n!() { ::// }';
|
||||
testProxyUrl(env, 'Crazy \n!() { ::// }', 'http://wow');
|
||||
|
||||
// The implementation assumes that the HTTP_PROXY environment variable is
|
||||
// somewhat reasonable, and if the scheme is missing, it is added.
|
||||
// Garbage in, garbage out!
|
||||
env.HTTP_PROXY = 'crazy without colon slash slash';
|
||||
testProxyUrl(env, 'http://crazy without colon slash slash', 'http://wow');
|
||||
});
|
||||
|
||||
describe('https_proxy and HTTPS_PROXY', function () {
|
||||
const env = {};
|
||||
// Assert that there is no fall back to http_proxy
|
||||
env.HTTP_PROXY = 'http://unexpected.proxy';
|
||||
testProxyUrl(env, '', 'https://example', null, 'https URL is not proxied when only HTTP_PROXY is set');
|
||||
|
||||
env.HTTPS_PROXY = 'http://https-proxy';
|
||||
testProxyUrl(env, 'http://https-proxy', 'https://example');
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
env.https_proxy = 'http://priority';
|
||||
testProxyUrl(env, 'http://priority', 'https://example', null, 'https_proxy takes precedence over HTTPS_PROXY');
|
||||
});
|
||||
|
||||
describe('ftp_proxy', function () {
|
||||
const env = {};
|
||||
// Something else than http_proxy / https, as a sanity check.
|
||||
env.FTP_PROXY = 'http://ftp-proxy';
|
||||
|
||||
testProxyUrl(env, 'http://ftp-proxy', 'ftp://example');
|
||||
testProxyUrl(env, '', 'ftps://example');
|
||||
});
|
||||
|
||||
describe('ws_proxy', function () {
|
||||
const env = {};
|
||||
// Something else than http_proxy / https, as a sanity check.
|
||||
env.ws_proxy = 'ws://ws-proxy';
|
||||
|
||||
testProxyUrl(env, 'ws://ws-proxy', 'ws://example1');
|
||||
testProxyUrl(env, '', 'wss://example2');
|
||||
});
|
||||
|
||||
describe('mqtt_proxy', function () {
|
||||
const env = {};
|
||||
// Something else than http_proxy / https, as a sanity check.
|
||||
env.mqtt_proxy = 'tcp://mqtt-proxy';
|
||||
env.no_proxy = 'direct';
|
||||
|
||||
testProxyUrl(env, '', 'mqtt://direct');
|
||||
testProxyUrl(env, 'tcp://mqtt-proxy', 'mqtt://example1');
|
||||
testProxyUrl(env, '', 'mqtts://example2');
|
||||
});
|
||||
|
||||
describe('all_proxy', function () {
|
||||
const env = {};
|
||||
env.ALL_PROXY = 'http://catch-all';
|
||||
testProxyUrl(env, 'http://catch-all', 'http://example');
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
env.all_proxy = 'http://priority';
|
||||
testProxyUrl(env, 'http://priority', 'https://example');
|
||||
});
|
||||
|
||||
describe('all_proxy without scheme', function () {
|
||||
const env = {};
|
||||
env.ALL_PROXY = 'noscheme';
|
||||
testProxyUrl(env, 'http://noscheme', 'http://example');
|
||||
testProxyUrl(env, 'https://noscheme', 'https://example');
|
||||
|
||||
// The module does not impose restrictions on the scheme.
|
||||
testProxyUrl(env, 'bogus-scheme://noscheme', 'bogus-scheme://example');
|
||||
|
||||
// But the URL should still be valid.
|
||||
testProxyUrl(env, '', 'bogus');
|
||||
});
|
||||
|
||||
describe('no_proxy empty', function () {
|
||||
const env = {};
|
||||
env.HTTPS_PROXY = 'http://i-am-proxy';
|
||||
|
||||
// NO_PROXY set but empty.
|
||||
env.NO_PROXY = '';
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'https://example1');
|
||||
|
||||
// No entries in NO_PROXY (comma).
|
||||
env.NO_PROXY = ',';
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'https://example2');
|
||||
|
||||
// No entries in NO_PROXY (whitespace).
|
||||
env.NO_PROXY = ' ';
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'https://example3');
|
||||
|
||||
// No entries in NO_PROXY (multiple whitespace / commas).
|
||||
env.NO_PROXY = ',\t,,,\n, ,\r';
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'https://example4');
|
||||
});
|
||||
|
||||
describe('no_proxy=example (single host)', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = 'example';
|
||||
testProxyUrl(env, '', 'http://example');
|
||||
testProxyUrl(env, '', 'http://example:80');
|
||||
testProxyUrl(env, '', 'http://example:0');
|
||||
testProxyUrl(env, '', 'http://example:1337');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://sub.example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://prefexample');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example.no');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://a.b.example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://host/example');
|
||||
});
|
||||
|
||||
describe('no_proxy=sub.example (subdomain)', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = 'sub.example';
|
||||
testProxyUrl(env, '', 'http://sub.example');
|
||||
testProxyUrl(env, '', 'http://sub.example:80');
|
||||
testProxyUrl(env, '', 'http://sub.example:1337');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example:80');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example:1337');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://bus.example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://bus.example:80');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://bus.example:1337');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://prefexample');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://a.b.example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example.no');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://host/example');
|
||||
});
|
||||
|
||||
describe('no_proxy=example:80 (host + port)', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = 'example:80';
|
||||
testProxyUrl(env, '', 'http://example');
|
||||
testProxyUrl(env, '', 'http://example:80');
|
||||
testProxyUrl(env, '', 'http://example:0');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example:1337');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://sub.example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://prefexample');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example.no');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://a.b.example');
|
||||
});
|
||||
|
||||
describe('no_proxy=.example (host suffix)', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = '.example';
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example:80');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example:1337');
|
||||
testProxyUrl(env, '', 'http://sub.example');
|
||||
testProxyUrl(env, '', 'http://sub.example:80');
|
||||
testProxyUrl(env, '', 'http://sub.example:1337');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://prefexample');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example.no');
|
||||
testProxyUrl(env, '', 'http://a.b.example');
|
||||
});
|
||||
|
||||
describe('no_proxy=.example (host suffix + port)', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = '.example:8080';
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example:80');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example:8080');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://sub.example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://sub.example:80');
|
||||
testProxyUrl(env, '', 'http://sub.example:8080');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://prefexample');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example.no');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://a.b.example');
|
||||
testProxyUrl(env, '', 'http://a.b.example:8080');
|
||||
});
|
||||
|
||||
describe('no_proxy=*', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
env.HTTPS_PROXY = 'https://i-am-proxy';
|
||||
env.NO_PROXY = '*';
|
||||
testProxyUrl(env, '', 'http://example.com');
|
||||
testProxyUrl(env, '', 'http://example:80');
|
||||
testProxyUrl(env, '', 'http://example:1337');
|
||||
testProxyUrl(env, '', 'https://example.com');
|
||||
testProxyUrl(env, '', 'https://example:443');
|
||||
});
|
||||
|
||||
describe('no_proxy=*.example (host suffix with *.)', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = '*.example';
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example:80');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example:1337');
|
||||
testProxyUrl(env, '', 'http://sub.example');
|
||||
testProxyUrl(env, '', 'http://sub.example:80');
|
||||
testProxyUrl(env, '', 'http://sub.example:1337');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://prefexample');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example.no');
|
||||
testProxyUrl(env, '', 'http://a.b.example');
|
||||
});
|
||||
|
||||
describe('no_proxy=*example (substring suffix)', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = '*example';
|
||||
const t = getProxyForUrl('http://example', { env });
|
||||
testProxyUrl(env, '', 'http://example');
|
||||
testProxyUrl(env, '', 'http://example:80');
|
||||
testProxyUrl(env, '', 'http://example:1337');
|
||||
testProxyUrl(env, '', 'http://sub.example');
|
||||
testProxyUrl(env, '', 'http://sub.example:80');
|
||||
testProxyUrl(env, '', 'http://sub.example:1337');
|
||||
testProxyUrl(env, '', 'http://prefexample');
|
||||
testProxyUrl(env, '', 'http://a.b.example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example.no');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://host/example');
|
||||
});
|
||||
|
||||
describe('no_proxy=.*example (arbitrary wildcards are NOT supported)',
|
||||
function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = '.*example';
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://sub.example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://prefexample');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://x.prefexample');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://a.b.example');
|
||||
});
|
||||
|
||||
describe('no_proxy=[::1],[::2]:80,10.0.0.1,10.0.0.2:80 (IP addresses)',
|
||||
function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = '[::1],[::2]:80,10.0.0.1,10.0.0.2:80';
|
||||
testProxyUrl(env, '', 'http://[::1]/');
|
||||
testProxyUrl(env, '', 'http://[::1]:80/');
|
||||
testProxyUrl(env, '', 'http://[::1]:1337/');
|
||||
|
||||
testProxyUrl(env, '', 'http://[::2]/');
|
||||
testProxyUrl(env, '', 'http://[::2]:80/');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://[::2]:1337/');
|
||||
|
||||
testProxyUrl(env, '', 'http://10.0.0.1/');
|
||||
testProxyUrl(env, '', 'http://10.0.0.1:80/');
|
||||
testProxyUrl(env, '', 'http://10.0.0.1:1337/');
|
||||
|
||||
testProxyUrl(env, '', 'http://10.0.0.2/');
|
||||
testProxyUrl(env, '', 'http://10.0.0.2:80/');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://10.0.0.2:1337/');
|
||||
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://10.0.0.3/');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://10.0.0.3:80/');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://10.0.0.3:1337/');
|
||||
});
|
||||
|
||||
describe('no_proxy=127.0.0.1/32 (CIDR is NOT supported)', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = '127.0.0.1/32';
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://127.0.0.1');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://127.0.0.1/32');
|
||||
});
|
||||
|
||||
describe('no_proxy=127.0.0.1 does NOT match localhost', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
|
||||
env.NO_PROXY = '127.0.0.1';
|
||||
testProxyUrl(env, '', 'http://127.0.0.1');
|
||||
// We're not performing DNS queries, so this shouldn't match.
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://localhost');
|
||||
});
|
||||
|
||||
describe('no_proxy with protocols that have a default port', function () {
|
||||
const env = {};
|
||||
env.MQTT_PROXY = 'http://mqtt';
|
||||
env.MQTTS_PROXY = 'https://m_q_t_t_s_proxy';
|
||||
env.WS_PROXY = 'http://ws';
|
||||
env.WSS_PROXY = 'http://wss';
|
||||
env.HTTP_PROXY = 'http://http';
|
||||
env.HTTPS_PROXY = 'http://https';
|
||||
env.GOPHER_PROXY = 'http://gopher';
|
||||
env.FTP_PROXY = 'http://ftp';
|
||||
env.ALL_PROXY = 'http://all';
|
||||
|
||||
env.NO_PROXY = 'xxx:21,xxx:70,xxx:80,xxx:443,xxx:1880,xxx:8880';
|
||||
|
||||
testProxyUrl(env, '', 'http://xxx');
|
||||
testProxyUrl(env, '', 'http://xxx:80');
|
||||
testProxyUrl(env, 'http://http', 'http://xxx:1337');
|
||||
|
||||
testProxyUrl(env, '', 'ws://xxx');
|
||||
testProxyUrl(env, '', 'ws://xxx:80');
|
||||
testProxyUrl(env, 'http://ws', 'ws://xxx:1337');
|
||||
|
||||
testProxyUrl(env, '', 'https://xxx');
|
||||
testProxyUrl(env, '', 'https://xxx:443');
|
||||
testProxyUrl(env, 'http://https', 'https://xxx:1337');
|
||||
|
||||
testProxyUrl(env, '', 'wss://xxx');
|
||||
testProxyUrl(env, '', 'wss://xxx:443');
|
||||
testProxyUrl(env, 'http://wss', 'wss://xxx:1337');
|
||||
|
||||
testProxyUrl(env, '', 'gopher://xxx');
|
||||
testProxyUrl(env, '', 'gopher://xxx:70');
|
||||
testProxyUrl(env, 'http://gopher', 'gopher://xxx:1337');
|
||||
|
||||
testProxyUrl(env, '', 'ftp://xxx');
|
||||
testProxyUrl(env, '', 'ftp://xxx:21');
|
||||
testProxyUrl(env, 'http://ftp', 'ftp://xxx:1337');
|
||||
|
||||
testProxyUrl(env, '', 'mqtt://xxx');
|
||||
testProxyUrl(env, '', 'mqtt://xxx:1880');
|
||||
testProxyUrl(env, 'http://mqtt', 'mqtt://xxx:1337');
|
||||
|
||||
testProxyUrl(env, 'http://mqtt', 'mqtt://yyy');
|
||||
testProxyUrl(env, 'http://mqtt', 'mqtt://yyy:1880');
|
||||
|
||||
testProxyUrl(env, 'http://all', 'unknown://xxx');
|
||||
testProxyUrl(env, 'http://all', 'unknown://xxx:1234');
|
||||
});
|
||||
|
||||
describe('no_proxy should not be case-sensitive', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
env.NO_PROXY = 'XXX,YYY,ZzZ';
|
||||
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://abc');
|
||||
testProxyUrl(env, '', 'http://xxx');
|
||||
testProxyUrl(env, '', 'http://XXX');
|
||||
testProxyUrl(env, '', 'http://yyy');
|
||||
testProxyUrl(env, '', 'http://YYY');
|
||||
testProxyUrl(env, '', 'http://ZzZ');
|
||||
testProxyUrl(env, '', 'http://zZz');
|
||||
});
|
||||
|
||||
describe('no_proxy should accept space separated entries', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
env.NO_PROXY = 'X X X,Y Y Y,Z z Z';
|
||||
|
||||
testProxyUrl(env, '', 'http://x x x');
|
||||
testProxyUrl(env, '', 'http://X X X');
|
||||
testProxyUrl(env, '', 'http://y y y');
|
||||
testProxyUrl(env, '', 'http://Y Y Y');
|
||||
testProxyUrl(env, '', 'http://Z z Z');
|
||||
testProxyUrl(env, '', 'http://z Z z');
|
||||
});
|
||||
|
||||
describe('NPM proxy configuration', function () {
|
||||
describe('npm_config_http_proxy should work', function () {
|
||||
const env = {};
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_http_proxy = 'http://http-proxy';
|
||||
|
||||
testProxyUrl(env, '', 'https://example');
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://example');
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_http_proxy = 'http://priority';
|
||||
testProxyUrl(env, 'http://priority', 'http://example');
|
||||
});
|
||||
// eslint-disable-next-line max-len
|
||||
describe('npm_config_http_proxy should take precedence over HTTP_PROXY and npm_config_proxy', function () {
|
||||
const env = {};
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_http_proxy = 'http://http-proxy';
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_proxy = 'http://unexpected-proxy';
|
||||
env.HTTP_PROXY = 'http://unexpected-proxy';
|
||||
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://example');
|
||||
});
|
||||
describe('npm_config_https_proxy should work', function () {
|
||||
const env = {};
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_http_proxy = 'http://unexpected.proxy';
|
||||
testProxyUrl(env, '', 'https://example');
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_https_proxy = 'http://https-proxy';
|
||||
testProxyUrl(env, 'http://https-proxy', 'https://example');
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_https_proxy = 'http://priority';
|
||||
testProxyUrl(env, 'http://priority', 'https://example');
|
||||
});
|
||||
// eslint-disable-next-line max-len
|
||||
describe('npm_config_https_proxy should take precedence over HTTPS_PROXY and npm_config_proxy', function () {
|
||||
const env = {};
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_https_proxy = 'http://https-proxy';
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_proxy = 'http://unexpected-proxy';
|
||||
env.HTTPS_PROXY = 'http://unexpected-proxy';
|
||||
|
||||
testProxyUrl(env, 'http://https-proxy', 'https://example');
|
||||
});
|
||||
describe('npm_config_proxy should work', function () {
|
||||
const env = {};
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_proxy = 'http://http-proxy';
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://example');
|
||||
testProxyUrl(env, 'http://http-proxy', 'https://example');
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_proxy = 'http://priority';
|
||||
testProxyUrl(env, 'http://priority', 'http://example');
|
||||
testProxyUrl(env, 'http://priority', 'https://example');
|
||||
});
|
||||
// eslint-disable-next-line max-len
|
||||
describe('HTTP_PROXY and HTTPS_PROXY should take precedence over npm_config_proxy', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://http-proxy';
|
||||
env.HTTPS_PROXY = 'http://https-proxy';
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_proxy = 'http://unexpected-proxy';
|
||||
testProxyUrl(env, 'http://http-proxy', 'http://example');
|
||||
testProxyUrl(env, 'http://https-proxy', 'https://example');
|
||||
});
|
||||
describe('npm_config_no_proxy should work', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_no_proxy = 'example';
|
||||
|
||||
testProxyUrl(env, '', 'http://example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://otherwebsite');
|
||||
});
|
||||
// eslint-disable-next-line max-len
|
||||
describe('npm_config_no_proxy should take precedence over NO_PROXY', function () {
|
||||
const env = {};
|
||||
env.HTTP_PROXY = 'http://i-am-proxy';
|
||||
env.NO_PROXY = 'otherwebsite';
|
||||
// eslint-disable-next-line camelcase
|
||||
env.npm_config_no_proxy = 'example';
|
||||
|
||||
testProxyUrl(env, '', 'http://example');
|
||||
testProxyUrl(env, 'http://i-am-proxy', 'http://otherwebsite');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user