mqtt-reqres-broker
Version:
mqtt request/response websocket broker
307 lines (243 loc) • 7.55 kB
JavaScript
// ## brokerSpec
// usage: $ DEBUG=brokerSpec mocha
const debug = require('debug')('mqtt-reqres-broker');
const assert = require('assert');
const MqttReqResBroker = require('../lib/mqtt-reqres-broker.js');
const mqtt = require('mqtt');
const serverOptions = {
hostname: 'localhost',
port: 9999
};
const wsBroker = new MqttReqResBroker();
var mqttClientA, mqttClientB;
const clientAId = 'client-a',
clientBId = 'client-b';
const clientAChannelId = 'client-a-channel-id';
function pcatch (reason) {
debug(reason);
assert(!reason, reason);
}
function startServer (callback) {
debug('startServer()');
wsBroker.initialize(serverOptions, err => {
if (err) {
console.error(err.stack);
callback(err);
}
else {
wsBroker.start(null, err => {
if (err) {
console.error(err.stack);
}
else {
console.log(`broker running at http://${wsBroker.hostname}:${wsBroker.port}/`);
}
callback(err);
});
}
});
}
function connectClient (clientId) {
return mqtt.connect({
protocol: 'ws',
host: serverOptions.hostname,
port: serverOptions.port,
clientId: clientId || Math.random().toString(36).substr(2, 23)
});
}
function onClientConnect (brokerClient) {
assert.strictEqual(brokerClient.clientId, onClientConnect.mqttClient.clientId);
setTimeout(() => {
assert(onClientConnect.mqttClient.connected, 'mqtt client should be connected');
onClientConnect.callbackFn();
}, 100);
}
onClientConnect.configure = function (mqttClient, callbackFn) {
onClientConnect.mqttClient = mqttClient;
onClientConnect.callbackFn = callbackFn;
};
describe('broker', function () {
before(done => {
startServer(err => {
wsBroker.mqtt.on('client', onClientConnect);
done();
});
});
after(done => {
wsBroker.close(done);
});
describe('connecting mqtt clients to broker via websockets', () => {
it('should connect to mqtt client A', done => {
mqttClientA = connectClient(clientAId);
onClientConnect.configure(mqttClientA, done);
});
it('should connect to mqtt client B', done => {
mqttClientB = connectClient(clientBId);
onClientConnect.configure(mqttClientB, done);
});
});
describe('client subscriptions on topic "connect/<device-id>/+"', () => {
it('should client A subscribe on topic "connect/' + clientAId + '/+"', done => {
mqttClientA.subscribe(
`connect/${clientAId}/+`,
{qos: 0},
function (err, granted) {
assert(!err);
assert.strictEqual(granted[0].qos, 0);
done();
}
);
});
it('should client B subscribe on topic "connect/' + clientBId + '/+"', done => {
mqttClientA.subscribe(
`connect/${clientBId}/+`,
{qos: 0},
function (err, granted) {
assert(!err);
assert.strictEqual(granted[0].qos, 0);
done();
}
);
});
it('should refuse to subscribe -t connect on invalid topics', done => {
function promiseTest (topic) {
return new Promise(function (resolve) {
mqttClientA.subscribe(
topic,
{qos: 0},
function (err, granted) {
assert(!err);
assert(granted[0].qos > 2, 'should not have subscribed -t connect on topic ' + topic);
resolve();
}
);
});
}
Promise.all(
[
// `connect/${clientBId}/+`,
`invalid/${clientBId}/+`,
`connect`,
`connect/`,
`connect//`,
`connect/+`,
`connect/#`,
`connect/${clientBId}/#`,
`connect/${clientBId}/`,
`connect/${clientBId}`,
`connect/${clientBId}/+/invalid`,
`connect/${clientBId}/invalid/+`
].map(promiseTest)
)
.then(() => done())
.catch(pcatch);
});
});
describe('connecting client A to client B', () => {
it('should subscribe -t message/' + clientBId + '/' + clientAChannelId + '/# ', done => {
/*
open a message channel to enable client-a to send messages:
subscribe -t message/<client-b-id>/<client-a-channel-id>/#
*/
mqttClientA.subscribe(
`message/${clientBId}/${clientAChannelId}/#`,
{qos: 0},
function (err, granted) {
assert(!err);
assert.strictEqual(granted[0].qos, 0);
done();
}
);
});
it('should refuse to subscribe -t message on invalid topics', done => {
function promiseTest (topic) {
return new Promise(function (resolve) {
mqttClientA.subscribe(
topic,
{qos: 0},
function (err, granted) {
assert(!err);
assert(granted[0].qos > 2, 'should not have subscribed -t message on topic ' + topic);
resolve();
}
);
});
}
Promise.all(
[
// `message/${clientBId}/${clientAChannelId}/#`,
`invalid/${clientBId}/${clientAChannelId}/#`,
`message`,
`message/`,
`message//`,
`message//+`,
`message/#`,
`message/${clientBId}/#`,
`message/${clientBId}/${clientAChannelId}/+`,
`message/${clientBId}/${clientAChannelId}/+/bar`,
`message/${clientBId}/${clientAChannelId}/foo/bar`,
`message/from-client-id/`,
`message/from-client-id`,
`message/from-client-id/+`,
`message/from-client-id/invalid/+`
].map(promiseTest)
)
.then(() => done())
.catch(pcatch);
});
it(`should publish -t connect/${clientBId}/${clientAId} -m ${clientAChannelId}`, done => {
/*
client-a publishes client-a-channel-id as message to client-b:
publish -t connect/<client-b-id>/<client-a-id> -m <client-a-channel-id>
*/
mqttClientA.publish(
// topic
`connect/${clientBId}/${clientAId}`,
// message
clientAChannelId,
// options
{qos: 0},
// callback
function (err) {
assert(!err);
done();
}
);
});
});
describe('sending messages between clients', () => {
/*
sending messages
publish -t message/<from>/<to>
publish -t message/<from-device-id>/<to-channel-id>
publish -t message/<client-b-id>/<client-a-channel-id>/foo -m '{packetId:123}'
*/
it('should enable client B to send messages to client A', done => {
mqttClientA.on('message', function (topic, payload) {
if (topic === `message/${clientBId}/${clientAChannelId}/foo`) {
assert.strictEqual(payload.toString(), 'bar');
done();
}
});
mqttClientB.publish(
// topic
`message/${clientBId}/${clientAChannelId}/foo`,
// message
'bar',
// options
{qos: 0}
);
});
});
describe('unsubscription of clients', () => {
it('should unsubscribe client A', done => {
assert(mqttClientA.connected);
mqttClientA.end(false, () => {
assert(!mqttClientA.connected);
mqttClientB.end(false, () => {
done();
});
});
});
});
});