首次提交
This commit is contained in:
+1177
File diff suppressed because it is too large
Load Diff
+865
@@ -0,0 +1,865 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const eos = require('end-of-stream')
|
||||
const { setup, connect, subscribe, subscribeMultiple, noError } = require('./helper')
|
||||
const aedes = require('../')
|
||||
const proxyquire = require('proxyquire')
|
||||
|
||||
test('test aedes.createBroker', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes.createBroker()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
connect(setup(broker), {}, function () {
|
||||
t.pass('connected')
|
||||
})
|
||||
})
|
||||
|
||||
test('publish QoS 0', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const s = connect(setup(), { clientId: 'my-client-xyz-5' })
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
retain: false,
|
||||
dup: false,
|
||||
clientId: 'my-client-xyz-5'
|
||||
}
|
||||
|
||||
s.broker.mq.on('hello', function (packet, cb) {
|
||||
expected.brokerId = s.broker.id
|
||||
expected.brokerCounter = s.broker.counter
|
||||
t.equal(packet.messageId, undefined, 'MUST not contain a packet identifier in QoS 0')
|
||||
t.same(packet, expected, 'packet matches')
|
||||
cb()
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world'
|
||||
})
|
||||
})
|
||||
|
||||
test('messageId shoud reset to 1 if it reached 65535', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const publishPacket = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
}
|
||||
let count = 0
|
||||
s.broker.on('clientReady', function (client) {
|
||||
subscribe(t, s, 'hello', 1, function () {
|
||||
client._nextId = 65535
|
||||
s.outStream.on('data', function (packet) {
|
||||
if (packet.cmd === 'puback') {
|
||||
t.equal(packet.messageId, 42)
|
||||
}
|
||||
if (packet.cmd === 'publish') {
|
||||
t.equal(packet.messageId, count++ === 0 ? 65535 : 1)
|
||||
}
|
||||
})
|
||||
s.inStream.write(publishPacket)
|
||||
s.inStream.write(publishPacket)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('publish empty topic throws error', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: '',
|
||||
payload: 'world'
|
||||
})
|
||||
|
||||
s.broker.on('clientError', function (client, err) {
|
||||
t.pass('should emit error')
|
||||
})
|
||||
})
|
||||
|
||||
test('publish to $SYS topic throws error', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: '$SYS/not/allowed',
|
||||
payload: 'world'
|
||||
})
|
||||
|
||||
s.broker.on('clientError', function (client, err) {
|
||||
t.pass('should emit error')
|
||||
})
|
||||
})
|
||||
|
||||
;[{ qos: 0, clean: false }, { qos: 0, clean: true }, { qos: 1, clean: false }, { qos: 1, clean: true }].forEach(function (ele) {
|
||||
test('subscribe a single topic in QoS ' + ele.qos + ' [clean=' + ele.clean + ']', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const s = connect(setup(), { clean: ele.clean })
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
dup: false,
|
||||
length: 12,
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
const expectedSubs = ele.clean ? null : [{ topic: 'hello', qos: ele.qos, rh: undefined, rap: undefined, nl: undefined }]
|
||||
|
||||
subscribe(t, s, 'hello', ele.qos, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet matches')
|
||||
})
|
||||
|
||||
s.broker.persistence.subscriptionsByClient(s.client, function (_, subs) {
|
||||
t.same(subs, expectedSubs)
|
||||
})
|
||||
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Catch invalid packet writeToStream errors
|
||||
test('return write errors to callback', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const write = proxyquire('../lib/write.js', {
|
||||
'mqtt-packet': {
|
||||
writeToStream: () => {
|
||||
throw Error('error')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const client = {
|
||||
conn: {
|
||||
writable: true
|
||||
},
|
||||
connecting: true
|
||||
}
|
||||
|
||||
write(client, {}, function (err) {
|
||||
t.equal(err.message, 'packet received not valid', 'should return the error to callback')
|
||||
})
|
||||
})
|
||||
|
||||
;[{ qos: 0, clean: false }, { qos: 0, clean: true }, { qos: 1, clean: false }, { qos: 1, clean: true }].forEach(function (ele) {
|
||||
test('subscribe multipe topics in QoS ' + ele.qos + ' [clean=' + ele.clean + ']', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const s = connect(setup(), { clean: ele.clean })
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
dup: false,
|
||||
length: 12,
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
const subs = [
|
||||
{ topic: 'hello', qos: ele.qos, rh: undefined, rap: undefined, nl: undefined },
|
||||
{ topic: 'world', qos: ele.qos, rh: undefined, rap: undefined, nl: undefined }
|
||||
]
|
||||
const expectedSubs = ele.clean ? null : subs
|
||||
|
||||
subscribeMultiple(t, s, subs, [ele.qos, ele.qos], function () {
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.same(packet, expected, 'packet matches')
|
||||
})
|
||||
|
||||
s.broker.persistence.subscriptionsByClient(s.client, function (_, saveSubs) {
|
||||
t.same(saveSubs, expectedSubs)
|
||||
})
|
||||
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('does not die badly on connection error', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'subscribe',
|
||||
messageId: 42,
|
||||
subscriptions: [{
|
||||
topic: 'hello',
|
||||
qos: 0
|
||||
}]
|
||||
})
|
||||
|
||||
s.broker.on('clientError', function (client, err) {
|
||||
t.ok(client, 'client is passed')
|
||||
t.ok(err, 'err is passed')
|
||||
})
|
||||
|
||||
s.outStream.on('data', function (packet) {
|
||||
s.conn.destroy()
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world')
|
||||
}, function () {
|
||||
t.pass('calls the callback')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Guarded in mqtt-packet
|
||||
test('subscribe should have messageId', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'subscribe',
|
||||
subscriptions: [{
|
||||
topic: 'hello',
|
||||
qos: 0
|
||||
}]
|
||||
})
|
||||
s.broker.on('connectionError', function (client, err) {
|
||||
t.ok(err.message, 'Invalid messageId')
|
||||
})
|
||||
})
|
||||
|
||||
test('subscribe with messageId 0 should return suback', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'subscribe',
|
||||
subscriptions: [{
|
||||
topic: 'hello',
|
||||
qos: 0
|
||||
}],
|
||||
messageId: 0
|
||||
})
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'suback',
|
||||
messageId: 0,
|
||||
dup: false,
|
||||
length: 3,
|
||||
qos: 0,
|
||||
retain: false,
|
||||
granted: [
|
||||
0
|
||||
]
|
||||
}, 'packet matches')
|
||||
})
|
||||
})
|
||||
|
||||
test('unsubscribe', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const s = noError(connect(setup()), t)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
s.inStream.write({
|
||||
cmd: 'unsubscribe',
|
||||
messageId: 43,
|
||||
unsubscriptions: ['hello']
|
||||
})
|
||||
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'unsuback',
|
||||
messageId: 43,
|
||||
dup: false,
|
||||
length: 2,
|
||||
qos: 0,
|
||||
retain: false
|
||||
}, 'packet matches')
|
||||
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.fail('packet received')
|
||||
})
|
||||
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world'
|
||||
}, function () {
|
||||
t.pass('publish finished')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('unsubscribe without subscribe', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = noError(connect(setup()), t)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'unsubscribe',
|
||||
messageId: 43,
|
||||
unsubscriptions: ['hello']
|
||||
})
|
||||
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'unsuback',
|
||||
messageId: 43,
|
||||
dup: false,
|
||||
length: 2,
|
||||
qos: 0,
|
||||
retain: false
|
||||
}, 'packet matches')
|
||||
})
|
||||
})
|
||||
|
||||
test('unsubscribe on disconnect for a clean=true client', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const opts = { clean: true }
|
||||
const s = connect(setup(), opts)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
s.conn.destroy(null, function () {
|
||||
t.pass('closed streams')
|
||||
})
|
||||
s.outStream.on('data', function () {
|
||||
t.fail('should not receive any more messages')
|
||||
})
|
||||
s.broker.once('unsubscribe', function () {
|
||||
t.pass('should emit unsubscribe')
|
||||
})
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world')
|
||||
}, function () {
|
||||
t.pass('calls the callback')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('unsubscribe on disconnect for a clean=false client', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const opts = { clean: false }
|
||||
const s = connect(setup(), opts)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
s.conn.destroy(null, function () {
|
||||
t.pass('closed streams')
|
||||
})
|
||||
s.outStream.on('data', function () {
|
||||
t.fail('should not receive any more messages')
|
||||
})
|
||||
s.broker.once('unsubscribe', function () {
|
||||
t.fail('should not emit unsubscribe')
|
||||
})
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world')
|
||||
}, function () {
|
||||
t.pass('calls the callback')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('disconnect', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = noError(connect(setup()), t)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('clientDisconnect', function () {
|
||||
t.pass('closed stream')
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'disconnect'
|
||||
})
|
||||
})
|
||||
|
||||
test('disconnect client on wrong cmd', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = noError(connect(setup()), t)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('clientDisconnect', function () {
|
||||
t.pass('closed stream')
|
||||
})
|
||||
|
||||
s.broker.on('clientReady', function (c) {
|
||||
// don't use stream write here because it will throw an error on mqtt_packet genetete
|
||||
c._parser.emit('packet', { cmd: 'pippo' })
|
||||
})
|
||||
})
|
||||
|
||||
test('client closes', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
const client = noError(connect(setup(broker), { clientId: 'abcde' }))
|
||||
broker.on('clientReady', function () {
|
||||
const brokerClient = broker.clients.abcde
|
||||
t.equal(brokerClient.connected, true, 'client connected')
|
||||
eos(client.conn, t.pass.bind(t, 'client closes'))
|
||||
setImmediate(() => {
|
||||
brokerClient.close(function () {
|
||||
t.equal(broker.clients.abcde, undefined, 'client instance is removed')
|
||||
})
|
||||
t.equal(brokerClient.connected, false, 'client disconnected')
|
||||
broker.close(function (err) {
|
||||
t.error(err, 'no error')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('broker closes', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
const client = noError(connect(setup(broker), {
|
||||
clientId: 'abcde'
|
||||
}, function () {
|
||||
eos(client.conn, t.pass.bind(t, 'client closes'))
|
||||
broker.close(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.ok(broker.closed)
|
||||
t.equal(broker.clients.abcde, undefined, 'client instance is removed')
|
||||
})
|
||||
}))
|
||||
})
|
||||
|
||||
test('broker closes gracefully', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const broker = aedes()
|
||||
const client1 = noError(connect(setup(broker), {
|
||||
}, function () {
|
||||
const client2 = noError(connect(setup(broker), {
|
||||
}, function () {
|
||||
t.equal(broker.connectedClients, 2, '2 connected clients')
|
||||
eos(client1.conn, t.pass.bind(t, 'client1 closes'))
|
||||
eos(client2.conn, t.pass.bind(t, 'client2 closes'))
|
||||
broker.close(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.ok(broker.mq.closed, 'broker mq closes')
|
||||
t.ok(broker.closed, 'broker closes')
|
||||
t.equal(broker.connectedClients, 0, 'no connected clients')
|
||||
})
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
test('testing other event', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const client = setup(broker)
|
||||
|
||||
broker.on('connectionError', function (client, error) {
|
||||
t.notOk(client.id, null)
|
||||
})
|
||||
client.conn.emit('error', 'Connect not yet arrived')
|
||||
})
|
||||
|
||||
test('connect without a clientId for MQTT 3.1.1', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = setup()
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
keepalive: 0
|
||||
})
|
||||
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'connack',
|
||||
returnCode: 0,
|
||||
length: 2,
|
||||
qos: 0,
|
||||
retain: false,
|
||||
dup: false,
|
||||
topic: null,
|
||||
payload: null,
|
||||
sessionPresent: false
|
||||
}, 'successful connack')
|
||||
})
|
||||
})
|
||||
|
||||
test('disconnect existing client with the same clientId', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const c1 = connect(setup(broker), {
|
||||
clientId: 'abcde'
|
||||
}, function () {
|
||||
eos(c1.conn, function () {
|
||||
t.pass('first client disconnected')
|
||||
})
|
||||
|
||||
connect(setup(broker), {
|
||||
clientId: 'abcde'
|
||||
}, function () {
|
||||
t.pass('second client connected')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('disconnect if another broker connects the same clientId', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const c1 = connect(setup(broker), {
|
||||
clientId: 'abcde'
|
||||
}, function () {
|
||||
eos(c1.conn, function () {
|
||||
t.pass('disconnect first client')
|
||||
})
|
||||
|
||||
broker.publish({
|
||||
topic: '$SYS/anotherBroker/new/clients',
|
||||
payload: Buffer.from('abcde')
|
||||
}, function () {
|
||||
t.pass('second client connects to another broker')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('publish to $SYS/broker/new/clients', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.mq.on('$SYS/' + broker.id + '/new/clients', function (packet, done) {
|
||||
t.equal(packet.payload.toString(), 'abcde', 'clientId matches')
|
||||
done()
|
||||
})
|
||||
|
||||
connect(setup(broker), {
|
||||
clientId: 'abcde'
|
||||
})
|
||||
})
|
||||
|
||||
test('publish to $SYS/broker/new/subsribers and $SYS/broker/new/unsubsribers', function (t) {
|
||||
t.plan(7)
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const sub = {
|
||||
topic: 'hello',
|
||||
qos: 0
|
||||
}
|
||||
|
||||
broker.mq.on('$SYS/' + broker.id + '/new/subscribes', function (packet, done) {
|
||||
const payload = JSON.parse(packet.payload.toString())
|
||||
t.equal(payload.clientId, 'abcde', 'clientId matches')
|
||||
t.same(payload.subs, [sub], 'subscriptions matches')
|
||||
done()
|
||||
})
|
||||
|
||||
broker.mq.on('$SYS/' + broker.id + '/new/unsubscribes', function (packet, done) {
|
||||
const payload = JSON.parse(packet.payload.toString())
|
||||
t.equal(payload.clientId, 'abcde', 'clientId matches')
|
||||
t.same(payload.subs, [sub.topic], 'unsubscriptions matches')
|
||||
done()
|
||||
})
|
||||
|
||||
const subscriber = connect(setup(broker), {
|
||||
clean: false, clientId: 'abcde'
|
||||
}, function () {
|
||||
subscribe(t, subscriber, sub.topic, sub.qos, function () {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'unsubscribe',
|
||||
messageId: 43,
|
||||
unsubscriptions: ['hello']
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('restore QoS 0 subscriptions not clean', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 12,
|
||||
retain: false
|
||||
}
|
||||
|
||||
let subscriber = connect(setup(broker), {
|
||||
clean: false, clientId: 'abcde'
|
||||
}, function () {
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker), {
|
||||
}, function () {
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
|
||||
t.equal(connect.sessionPresent, true, 'session present is set to true')
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 0
|
||||
})
|
||||
})
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('do not restore QoS 0 subscriptions when clean', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = connect(setup(broker), {
|
||||
clean: true, clientId: 'abcde'
|
||||
}, function () {
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.inStream.end()
|
||||
subscriber.broker.persistence.subscriptionsByClient(broker.clients.abcde, function (_, subs, client) {
|
||||
t.equal(subs, null, 'no previous subscriptions restored')
|
||||
})
|
||||
const publisher = connect(setup(broker), {
|
||||
}, function () {
|
||||
subscriber = connect(setup(broker), {
|
||||
clean: true, clientId: 'abcde'
|
||||
}, function (connect) {
|
||||
t.equal(connect.sessionPresent, false, 'session present is set to false')
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 0
|
||||
})
|
||||
})
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('packet received')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('double sub does not double deliver', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
dup: false,
|
||||
length: 12,
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
const s = connect(setup(), {
|
||||
}, function () {
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet matches')
|
||||
s.outStream.on('data', function () {
|
||||
t.fail('double deliver')
|
||||
})
|
||||
})
|
||||
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
})
|
||||
|
||||
test('overlapping sub does not double deliver', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
dup: false,
|
||||
length: 12,
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
const s = connect(setup(), {
|
||||
}, function () {
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
subscribe(t, s, 'hello/#', 0, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet matches')
|
||||
s.outStream.on('data', function () {
|
||||
t.fail('double deliver')
|
||||
})
|
||||
})
|
||||
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
})
|
||||
|
||||
test('clear drain', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const s = connect(setup(), {
|
||||
}, function () {
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
// fake a busy socket
|
||||
s.conn.write = function (chunk, enc, cb) {
|
||||
return false
|
||||
}
|
||||
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world'
|
||||
}, function () {
|
||||
t.pass('callback called')
|
||||
})
|
||||
|
||||
s.conn.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
})
|
||||
|
||||
test('id option', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker1 = aedes()
|
||||
|
||||
setup(broker1).conn.destroy()
|
||||
t.ok(broker1.id, 'broker gets random id when id option not set')
|
||||
|
||||
const broker2 = aedes({ id: 'abc' })
|
||||
setup(broker2).conn.destroy()
|
||||
t.equal(broker2.id, 'abc', 'broker id equals id option when set')
|
||||
|
||||
t.teardown(() => {
|
||||
broker1.close()
|
||||
broker2.close()
|
||||
})
|
||||
})
|
||||
|
||||
test('not duplicate client close when client error occurs', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
connect(setup(broker))
|
||||
broker.on('client', function (client) {
|
||||
client.conn.on('drain', () => {
|
||||
t.pass('client closed ok')
|
||||
})
|
||||
client.close()
|
||||
// add back to test if there is duplicated close() call
|
||||
client.conn.on('drain', () => {
|
||||
t.fail('double client close calls')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('not duplicate client close when double close() called', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
connect(setup(broker))
|
||||
broker.on('clientReady', function (client) {
|
||||
client.conn.on('drain', () => {
|
||||
t.pass('client closed ok')
|
||||
})
|
||||
client.close()
|
||||
// add back to test if there is duplicated close() call
|
||||
client.conn.on('drain', () => {
|
||||
t.fail('double execute client close function')
|
||||
})
|
||||
client.close()
|
||||
})
|
||||
})
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { setup, connect, subscribe } = require('./helper')
|
||||
|
||||
for (const qos of [0, 1, 2]) {
|
||||
const packet = {
|
||||
qos,
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world'
|
||||
}
|
||||
|
||||
if (qos > 0) packet.messageId = 42
|
||||
|
||||
test('normal client sends a publish message and shall receive it back, qos = ' + qos, function (t) {
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const handle = setTimeout(() => {
|
||||
t.fail('did not receive packet back')
|
||||
t.end()
|
||||
}, 1000)
|
||||
|
||||
subscribe(t, s, 'hello', qos, function () {
|
||||
s.outStream.on('data', (packet) => {
|
||||
if (packet.cmd === 'publish') {
|
||||
clearTimeout(handle)
|
||||
t.end()
|
||||
} else if (packet.cmd === 'pubrec') {
|
||||
s.inStream.write({ cmd: 'pubrel', messageId: 42 })
|
||||
}
|
||||
})
|
||||
|
||||
s.inStream.write(packet)
|
||||
})
|
||||
})
|
||||
|
||||
test('bridge client sends a publish message but shall not receive it back, qos = ' + qos, function (t) {
|
||||
// protocolVersion 128 + 4 means mqtt 3.1.1 with bridgeMode enabled
|
||||
// https://github.com/mqttjs/mqtt-packet/blob/7f7c2ed8bcb4b2c582851d120a94e0b4a731f661/parser.js#L171
|
||||
const s = connect(setup(), { clientId: 'my-client-bridge-1', protocolVersion: 128 + 4 })
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const handle = setTimeout(() => t.end(), 1000)
|
||||
|
||||
subscribe(t, s, 'hello', qos, function () {
|
||||
s.outStream.on('data', function () {
|
||||
clearTimeout(handle)
|
||||
t.fail('should not receive packet back')
|
||||
t.end()
|
||||
})
|
||||
|
||||
s.inStream.write(packet)
|
||||
})
|
||||
})
|
||||
}
|
||||
+1027
File diff suppressed because it is too large
Load Diff
+177
@@ -0,0 +1,177 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const EventEmitter = require('events')
|
||||
const { setup, connect, subscribe } = require('./helper')
|
||||
const aedes = require('../')
|
||||
|
||||
test('aedes is closed before client authenticate returns', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const evt = new EventEmitter()
|
||||
const broker = aedes({
|
||||
authenticate: (client, username, password, done) => {
|
||||
evt.emit('AuthenticateBegin', client)
|
||||
setTimeout(function () {
|
||||
done(null, true)
|
||||
}, 2000)
|
||||
}
|
||||
})
|
||||
|
||||
broker.on('client', function (client) {
|
||||
t.fail('should no client registration')
|
||||
})
|
||||
broker.on('connackSent', function () {
|
||||
t.fail('should no connack be sent')
|
||||
})
|
||||
broker.on('clientError', function (client, err) {
|
||||
t.error(err)
|
||||
})
|
||||
|
||||
connect(setup(broker))
|
||||
|
||||
evt.on('AuthenticateBegin', function (client) {
|
||||
t.equal(broker.connectedClients, 0)
|
||||
broker.close()
|
||||
})
|
||||
})
|
||||
|
||||
test('client is closed before authenticate returns', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const evt = new EventEmitter()
|
||||
const broker = aedes({
|
||||
authenticate: async (client, username, password, done) => {
|
||||
evt.emit('AuthenticateBegin', client)
|
||||
setTimeout(function () {
|
||||
done(null, true)
|
||||
}, 2000)
|
||||
}
|
||||
})
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.on('client', function (client) {
|
||||
t.fail('should no client registration')
|
||||
})
|
||||
broker.on('connackSent', function () {
|
||||
t.fail('should no connack be sent')
|
||||
})
|
||||
broker.on('clientError', function (client, err) {
|
||||
t.error(err)
|
||||
})
|
||||
|
||||
connect(setup(broker))
|
||||
|
||||
evt.on('AuthenticateBegin', function (client) {
|
||||
t.equal(broker.connectedClients, 0)
|
||||
client.close()
|
||||
})
|
||||
})
|
||||
|
||||
test('client is closed before authorizePublish returns', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const evt = new EventEmitter()
|
||||
const broker = aedes({
|
||||
authorizePublish: (client, packet, done) => {
|
||||
evt.emit('AuthorizePublishBegin', client)
|
||||
// simulate latency writing to persistent store.
|
||||
setTimeout(function () {
|
||||
done()
|
||||
evt.emit('AuthorizePublishEnd', client)
|
||||
}, 2000)
|
||||
}
|
||||
})
|
||||
|
||||
broker.on('clientError', function (client, err) {
|
||||
t.equal(err.message, 'connection closed')
|
||||
})
|
||||
|
||||
const s = connect(setup(broker))
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 10,
|
||||
retain: false
|
||||
})
|
||||
|
||||
evt.on('AuthorizePublishBegin', function (client) {
|
||||
t.equal(broker.connectedClients, 1)
|
||||
client.close()
|
||||
})
|
||||
evt.on('AuthorizePublishEnd', function (client) {
|
||||
t.equal(broker.connectedClients, 0)
|
||||
broker.close()
|
||||
})
|
||||
})
|
||||
|
||||
test('close client when its socket is closed', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const subscriber = connect(setup(broker))
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.end()
|
||||
subscriber.conn.on('close', function () {
|
||||
t.equal(broker.connectedClients, 0, 'no connected client')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('multiple clients subscribe same topic, and all clients still receive message except the closed one', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const mqtt = require('mqtt')
|
||||
const broker = aedes()
|
||||
|
||||
let client2
|
||||
|
||||
t.teardown(() => {
|
||||
client2.end()
|
||||
broker.close()
|
||||
server.close()
|
||||
})
|
||||
|
||||
const server = require('net').createServer(broker.handle)
|
||||
const port = 1883
|
||||
server.listen(port)
|
||||
broker.on('clientError', function (client, err) {
|
||||
t.error(err)
|
||||
})
|
||||
|
||||
const _sameTopic = 'hello'
|
||||
|
||||
// client 1
|
||||
const client1 = mqtt.connect('mqtt://localhost', { clientId: 'client1', resubscribe: false, reconnectPeriod: -1 })
|
||||
client1.on('message', () => {
|
||||
t.fail('client1 receives message')
|
||||
})
|
||||
|
||||
client1.subscribe(_sameTopic, { qos: 0, retain: false }, () => {
|
||||
t.pass('client1 sub callback')
|
||||
// stimulate closed socket by users
|
||||
client1.stream.destroy()
|
||||
|
||||
// client 2
|
||||
client2 = mqtt.connect('mqtt://localhost', { clientId: 'client2', resubscribe: false })
|
||||
client2.on('message', () => {
|
||||
t.pass('client2 receives message')
|
||||
t.equal(broker.connectedClients, 1)
|
||||
})
|
||||
client2.subscribe(_sameTopic, { qos: 0, retain: false }, () => {
|
||||
t.pass('client2 sub callback')
|
||||
|
||||
// pubClient
|
||||
const pubClient = mqtt.connect('mqtt://localhost', { clientId: 'pubClient' })
|
||||
pubClient.publish(_sameTopic, 'world', { qos: 0, retain: false }, () => {
|
||||
t.pass('pubClient publish event')
|
||||
pubClient.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
+762
@@ -0,0 +1,762 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const http = require('http')
|
||||
const ws = require('websocket-stream')
|
||||
const mqtt = require('mqtt')
|
||||
const { setup, connect, delay } = require('./helper')
|
||||
const aedes = require('../')
|
||||
|
||||
;[{ ver: 3, id: 'MQIsdp' }, { ver: 4, id: 'MQTT' }].forEach(function (ele) {
|
||||
test('connect and connack (minimal)', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const s = setup()
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: ele.id,
|
||||
protocolVersion: ele.ver,
|
||||
clean: true,
|
||||
clientId: 'my-client',
|
||||
keepalive: 0
|
||||
})
|
||||
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'connack',
|
||||
returnCode: 0,
|
||||
length: 2,
|
||||
qos: 0,
|
||||
retain: false,
|
||||
dup: false,
|
||||
topic: null,
|
||||
payload: null,
|
||||
sessionPresent: false
|
||||
}, 'successful connack')
|
||||
t.equal(s.client.version, ele.ver)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.1.2-2]
|
||||
test('reject client requested for unacceptable protocol version', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQIsdp',
|
||||
protocolVersion: 5,
|
||||
clean: true,
|
||||
clientId: 'my-client',
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.equal(packet.cmd, 'connack')
|
||||
t.equal(packet.returnCode, 1, 'unacceptable protocol version')
|
||||
t.equal(broker.connectedClients, 0)
|
||||
})
|
||||
broker.on('clientError', function (client, err) {
|
||||
t.fail('should not raise clientError error')
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.equal(err.message, 'unacceptable protocol version')
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.1.2-1], Guarded in mqtt-packet
|
||||
test('reject client requested for unsupported protocol version', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 2,
|
||||
clean: true,
|
||||
clientId: 'my-client',
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.fail('no data sent')
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.equal(client.version, null)
|
||||
t.equal(err.message, 'Invalid protocol version')
|
||||
t.equal(broker.connectedClients, 0)
|
||||
})
|
||||
})
|
||||
|
||||
test('reject clients that exceed the keepalive limit', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes({
|
||||
keepaliveLimit: 100
|
||||
})
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
keepalive: 150
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
console.log(packet)
|
||||
t.same(packet, {
|
||||
cmd: 'connack',
|
||||
returnCode: 6,
|
||||
length: 2,
|
||||
qos: 0,
|
||||
retain: false,
|
||||
dup: false,
|
||||
topic: null,
|
||||
payload: null,
|
||||
sessionPresent: false
|
||||
}, 'unsuccessful connack, keep alive limit exceeded')
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.equal(err.message, 'keep alive limit exceeded')
|
||||
t.equal(broker.connectedClients, 0)
|
||||
})
|
||||
})
|
||||
|
||||
// Guarded in mqtt-packet
|
||||
test('reject clients with no clientId running on MQTT 3.1.0', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQIsdp',
|
||||
protocolVersion: 3,
|
||||
clean: true,
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.fail('no data sent')
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.equal(client.version, null)
|
||||
t.equal(err.message, 'clientId must be supplied before 3.1.1')
|
||||
t.equal(broker.connectedClients, 0)
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.1.3-7], Guarded in mqtt-packet
|
||||
test('reject clients without clientid and clean=false on MQTT 3.1.1', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: false,
|
||||
clientId: '',
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.fail('no data sent')
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.equal(err.message, 'clientId must be given if cleanSession set to 0')
|
||||
t.equal(broker.connectedClients, 0)
|
||||
})
|
||||
})
|
||||
|
||||
test('clients without clientid and clean=true on MQTT 3.1.1 will get a generated clientId', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.equal(packet.cmd, 'connack')
|
||||
t.equal(packet.returnCode, 0)
|
||||
t.equal(broker.connectedClients, 1)
|
||||
t.equal(s.client.version, 4)
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.error(err, 'no error')
|
||||
})
|
||||
broker.on('client', function (client) {
|
||||
t.ok(client.id.startsWith('aedes_'))
|
||||
})
|
||||
})
|
||||
|
||||
test('client connect error while fetching subscriptions', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
broker.persistence.subscriptionsByClient = function (c, cb) {
|
||||
cb(new Error('error'), [], c)
|
||||
}
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: false,
|
||||
clientId: 'my-client',
|
||||
keepalive: 0
|
||||
})
|
||||
|
||||
broker.on('clientError', function (client, err) {
|
||||
t.equal(client.version, 4)
|
||||
t.pass('throws error')
|
||||
})
|
||||
})
|
||||
|
||||
test('client connect clear outgoing', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const clientId = 'abcde'
|
||||
const brokerId = 'pippo'
|
||||
|
||||
const broker = aedes({ id: brokerId })
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const subs = [{ clientId }]
|
||||
const packet = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
brokerId,
|
||||
brokerCounter: 2,
|
||||
retain: true,
|
||||
messageId: 42,
|
||||
dup: false
|
||||
}
|
||||
|
||||
broker.persistence.outgoingEnqueueCombi(subs, packet, function () {
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId,
|
||||
keepalive: 0
|
||||
})
|
||||
|
||||
broker.on('clientReady', function (client) {
|
||||
broker.persistence.outgoingUpdate(client, packet, function (err) {
|
||||
t.equal('no such packet', err.message, 'packet not found')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('clients with zero-byte clientid and clean=true on MQTT 3.1.1 will get a generated clientId', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId: '',
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.equal(packet.cmd, 'connack')
|
||||
t.equal(packet.returnCode, 0)
|
||||
t.equal(broker.connectedClients, 1)
|
||||
t.equal(s.client.version, 4)
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.error(err, 'no error')
|
||||
})
|
||||
broker.on('client', function (client) {
|
||||
t.ok(client.id.startsWith('aedes_'))
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.1.3-7]
|
||||
test('reject clients with > 23 clientId length in MQTT 3.1.0', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
const conn = s.client.conn
|
||||
const end = conn.end
|
||||
|
||||
conn.end = function () {
|
||||
t.fail('should not call `conn.end()`')
|
||||
end()
|
||||
}
|
||||
|
||||
function drain () {
|
||||
t.pass('should empty connection request queue')
|
||||
}
|
||||
|
||||
conn._writableState.getBuffer = () => [{ callback: drain }, { callback: drain }]
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQIsdp',
|
||||
protocolVersion: 3,
|
||||
clean: true,
|
||||
clientId: 'abcdefghijklmnopqrstuvwxyz',
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.equal(packet.cmd, 'connack')
|
||||
t.equal(packet.returnCode, 2, 'identifier rejected')
|
||||
t.equal(broker.connectedClients, 0)
|
||||
t.equal(s.client.version, null)
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.equal(err.message, 'identifier rejected')
|
||||
})
|
||||
})
|
||||
|
||||
test('connect clients with > 23 clientId length using aedes maxClientsIdLength option in MQTT 3.1.0', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes({ maxClientsIdLength: 26 })
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 3,
|
||||
clean: true,
|
||||
clientId: 'abcdefghijklmnopqrstuvwxyz',
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.equal(packet.cmd, 'connack')
|
||||
t.equal(packet.returnCode, 0)
|
||||
t.equal(broker.connectedClients, 1)
|
||||
t.equal(s.client.version, 3)
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.error(err, 'no error')
|
||||
})
|
||||
})
|
||||
|
||||
test('connect with > 23 clientId length in MQTT 3.1.1', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId: 'abcdefghijklmnopqrstuvwxyz',
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.equal(packet.cmd, 'connack')
|
||||
t.equal(packet.returnCode, 0)
|
||||
t.equal(broker.connectedClients, 1)
|
||||
t.equal(s.client.version, 4)
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.error(err, 'no error')
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.1.0-1]
|
||||
test('the first Packet MUST be a CONNECT Packet', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const packet = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
const s = setup(broker)
|
||||
s.inStream.write(packet)
|
||||
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.equal(err.message, 'Invalid protocol')
|
||||
})
|
||||
setImmediate(() => {
|
||||
t.ok(s.conn.destroyed, 'close connection if first packet is not a CONNECT')
|
||||
s.conn.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.1.0-2]
|
||||
test('second CONNECT Packet sent from a Client as a protocol violation and disconnect the Client', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const packet = {
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId: 'my-client',
|
||||
keepalive: 0
|
||||
}
|
||||
broker.on('clientError', function (client, err) {
|
||||
t.equal(err.message, 'Invalid protocol')
|
||||
})
|
||||
const s = connect(setup(broker), { clientId: 'abcde' })
|
||||
s.broker.on('clientReady', function () {
|
||||
t.ok(broker.clients.abcde.connected)
|
||||
// destory client when there is a 2nd cmd:connect, even the clientId is dfferent
|
||||
s.inStream.write(packet)
|
||||
setImmediate(() => {
|
||||
t.equal(broker.clients.abcde, undefined, 'client instance is removed')
|
||||
t.ok(s.conn.destroyed, 'close connection if packet is a CONNECT after network is established')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('connect handler calls done when preConnect throws error', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes({
|
||||
preConnect: function (client, packet, done) {
|
||||
done(Error('error in preconnect'))
|
||||
}
|
||||
})
|
||||
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
const handleConnect = require('../lib/handlers/connect')
|
||||
|
||||
handleConnect(s.client, {}, function done (err) {
|
||||
t.equal(err.message, 'error in preconnect', 'calls done with error')
|
||||
})
|
||||
})
|
||||
|
||||
test('handler calls done when disconnect or unknown packet cmd is received', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker = aedes()
|
||||
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
const handle = require('../lib/handlers/index')
|
||||
|
||||
handle(s.client, { cmd: 'disconnect' }, function done () {
|
||||
t.pass('calls done when disconnect cmd is received')
|
||||
})
|
||||
|
||||
handle(s.client, { cmd: 'fsfadgragae' }, function done () {
|
||||
t.pass('calls done when unknown cmd is received')
|
||||
})
|
||||
})
|
||||
|
||||
test('reject second CONNECT Packet sent while first CONNECT still in preConnect stage', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const packet1 = {
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId: 'my-client-1',
|
||||
keepalive: 0
|
||||
}
|
||||
const packet2 = {
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId: 'my-client-2',
|
||||
keepalive: 0
|
||||
}
|
||||
|
||||
let i = 0
|
||||
const broker = aedes({
|
||||
preConnect: function (client, packet, done) {
|
||||
const ms = i++ === 0 ? 2000 : 500
|
||||
setTimeout(function () {
|
||||
done(null, true)
|
||||
}, ms)
|
||||
}
|
||||
})
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.equal(err.info.clientId, 'my-client-2')
|
||||
t.equal(err.message, 'Invalid protocol')
|
||||
})
|
||||
|
||||
const msg = async (s, ms, msg) => {
|
||||
await delay(ms)
|
||||
s.inStream.write(msg)
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
await Promise.all([msg(s, 100, packet1), msg(s, 200, packet2)])
|
||||
})().catch(
|
||||
(error) => {
|
||||
t.fail(error)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// [MQTT-3.1.2-1], Guarded in mqtt-packet
|
||||
test('reject clients with wrong protocol name', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT_hello',
|
||||
protocolVersion: 3,
|
||||
clean: true,
|
||||
clientId: 'my-client',
|
||||
keepalive: 0
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.fail('no data sent')
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
t.equal(err.message, 'Invalid protocolId')
|
||||
t.equal(broker.connectedClients, 0)
|
||||
})
|
||||
})
|
||||
|
||||
test('After first CONNECT Packet, others are queued until \'connect\' event', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const queueLimit = 50
|
||||
const broker = aedes({ queueLimit })
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publishP = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
|
||||
const connectP = {
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId: 'abcde',
|
||||
keepalive: 0
|
||||
}
|
||||
|
||||
const s = setup(broker)
|
||||
s.inStream.write(connectP)
|
||||
|
||||
process.once('warning', e => t.fail('Memory leak detected'))
|
||||
|
||||
for (let i = 0; i < queueLimit; i++) {
|
||||
s.inStream.write(publishP)
|
||||
}
|
||||
|
||||
broker.on('client', function (client) {
|
||||
t.equal(client._parser._queue.length, queueLimit, 'Packets have been queued')
|
||||
|
||||
client.once('connected', () => {
|
||||
t.equal(client._parser._queue, null, 'Queue is empty')
|
||||
s.conn.destroy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Test queue limit', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const queueLimit = 50
|
||||
const broker = aedes({ queueLimit })
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publishP = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
|
||||
const connectP = {
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId: 'abcde',
|
||||
keepalive: 0
|
||||
}
|
||||
|
||||
const s = setup(broker)
|
||||
s.inStream.write(connectP)
|
||||
|
||||
process.once('warning', e => t.fail('Memory leak detected'))
|
||||
|
||||
for (let i = 0; i < queueLimit + 1; i++) {
|
||||
s.inStream.write(publishP)
|
||||
}
|
||||
|
||||
broker.on('connectionError', function (conn, err) {
|
||||
t.equal(err.message, 'Client queue limit reached', 'Queue error is thrown')
|
||||
s.conn.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
;[['fail with no error msg', 3, null, false], ['succeed with no error msg', 9, null, true], ['fail with error msg', 6, new Error('connection banned'), false], ['succeed with error msg', 6, new Error('connection banned'), true]].forEach(function (ele, idx) {
|
||||
const title = ele[0]
|
||||
const plan = ele[1]
|
||||
const err = ele[2]
|
||||
const ok = ele[3]
|
||||
test('preConnect handler - ' + title, function (t) {
|
||||
t.plan(plan)
|
||||
|
||||
const broker = aedes({
|
||||
preConnect: function (client, packet, done) {
|
||||
t.ok(client.connecting)
|
||||
t.notOk(client.connected)
|
||||
t.equal(client.version, null)
|
||||
return done(err, ok)
|
||||
}
|
||||
})
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = setup(broker)
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId: 'my-client-' + idx,
|
||||
keepalive: 0
|
||||
})
|
||||
broker.on('client', function (client) {
|
||||
if (ok && !err) {
|
||||
t.ok(client.connecting)
|
||||
t.notOk(client.connected)
|
||||
t.pass('register client ok')
|
||||
} else {
|
||||
t.fail('no reach here')
|
||||
}
|
||||
})
|
||||
broker.on('clientReady', function (client) {
|
||||
t.notOk(client.connecting)
|
||||
t.ok(client.connected)
|
||||
t.pass('connect ok')
|
||||
})
|
||||
broker.on('clientError', function (client, err) {
|
||||
t.fail('no client error')
|
||||
})
|
||||
broker.on('connectionError', function (client, err) {
|
||||
if (err) {
|
||||
t.notOk(client.connecting)
|
||||
t.notOk(client.connected)
|
||||
t.equal(err.message, 'connection banned')
|
||||
} else {
|
||||
t.fail('no connection error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// websocket-stream based connections
|
||||
test('websocket clients have access to the request object', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const port = 4883
|
||||
const broker = aedes()
|
||||
broker.on('client', function (client) {
|
||||
if (client.req) {
|
||||
t.pass('client request object present')
|
||||
if (client.req.headers) {
|
||||
t.equal('sample', client.req.headers['x-test-protocol'])
|
||||
}
|
||||
} else {
|
||||
t.fail('no request object present')
|
||||
}
|
||||
})
|
||||
|
||||
const server = http.createServer()
|
||||
ws.createServer({
|
||||
server
|
||||
}, broker.handle)
|
||||
|
||||
server.listen(port, function (err) {
|
||||
t.error(err, 'no error')
|
||||
})
|
||||
|
||||
const client = mqtt.connect(`ws://localhost:${port}`, {
|
||||
wsOptions: {
|
||||
headers: {
|
||||
'X-Test-Protocol': 'sample'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.teardown(() => {
|
||||
client.end(true)
|
||||
broker.close()
|
||||
server.close()
|
||||
})
|
||||
})
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const mqemitter = require('mqemitter')
|
||||
const { setup, connect, subscribe } = require('./helper')
|
||||
const aedes = require('../')
|
||||
|
||||
test('publishes an hearbeat', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker = aedes({
|
||||
heartbeatInterval: 10 // ms
|
||||
})
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.subscribe('$SYS/+/heartbeat', function (message, cb) {
|
||||
const id = message.topic.match(/\$SYS\/([^/]+)\/heartbeat/)[1]
|
||||
t.equal(id, broker.id, 'broker id matches')
|
||||
t.same(message.payload.toString(), id, 'message has id as the payload')
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
test('publishes birth', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const mq = mqemitter()
|
||||
const brokerId = 'test-broker'
|
||||
const fakeBroker = 'fake-broker'
|
||||
const clientId = 'test-client'
|
||||
|
||||
mq.on(`$SYS/${brokerId}/birth`, (message, cb) => {
|
||||
t.pass('broker birth received')
|
||||
t.same(message.payload.toString(), brokerId, 'message has id as the payload')
|
||||
cb()
|
||||
})
|
||||
|
||||
const broker = aedes({
|
||||
id: brokerId,
|
||||
mq
|
||||
})
|
||||
|
||||
broker.on('client', (client) => {
|
||||
t.equal(client.id, clientId, 'client connected')
|
||||
// set a fake counter on a fake broker
|
||||
process.nextTick(() => {
|
||||
broker.clients[clientId].duplicates[fakeBroker] = 42
|
||||
mq.emit({ topic: `$SYS/${fakeBroker}/birth`, payload: Buffer.from(fakeBroker) })
|
||||
})
|
||||
})
|
||||
|
||||
mq.on(`$SYS/${fakeBroker}/birth`, (message, cb) => {
|
||||
process.nextTick(() => {
|
||||
t.equal(!!broker.clients[clientId].duplicates[fakeBroker], false, 'client duplicates has been resetted')
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
const s = connect(setup(broker), { clientId })
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
})
|
||||
|
||||
;['$mcollina', '$SYS'].forEach(function (topic) {
|
||||
test('does not forward $ prefixed topics to # subscription - ' + topic, function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
subscribe(t, s, '#', 0, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.fail('no packet should be received')
|
||||
})
|
||||
|
||||
s.broker.mq.emit({
|
||||
cmd: 'publish',
|
||||
topic: topic + '/hello',
|
||||
payload: 'world'
|
||||
}, function () {
|
||||
t.pass('nothing happened')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('does not forward $ prefixed topics to +/# subscription - ' + topic, function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
subscribe(t, s, '+/#', 0, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.fail('no packet should be received')
|
||||
})
|
||||
|
||||
s.broker.mq.emit({
|
||||
cmd: 'publish',
|
||||
topic: topic + '/hello',
|
||||
payload: 'world'
|
||||
}, function () {
|
||||
t.pass('nothing happened')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('does not store $SYS topics to QoS 1 # subscription', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const opts = { clean: false, clientId: 'abcde' }
|
||||
let s = connect(setup(broker), opts)
|
||||
|
||||
subscribe(t, s, '#', 1, function () {
|
||||
s.inStream.end()
|
||||
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: '$SYS/hello',
|
||||
payload: 'world',
|
||||
qos: 1
|
||||
}, function () {
|
||||
s = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.fail('no packet should be received')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Emit event when receives a ping', { timeout: 2000 }, function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.on('ping', function (packet, client) {
|
||||
if (client && client) {
|
||||
t.equal(client.id, 'abcde')
|
||||
t.equal(packet.cmd, 'pingreq')
|
||||
t.equal(packet.payload, null)
|
||||
t.equal(packet.topic, null)
|
||||
t.equal(packet.length, 0)
|
||||
}
|
||||
})
|
||||
|
||||
const s = connect(setup(broker), { clientId: 'abcde' })
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'pingreq'
|
||||
})
|
||||
})
|
||||
|
||||
test('Emit event when broker closed', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes()
|
||||
broker.once('closed', function () {
|
||||
t.ok(true)
|
||||
})
|
||||
broker.close()
|
||||
})
|
||||
|
||||
test('Emit closed event one only when double broker.close()', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
broker.on('closed', function () {
|
||||
t.pass('closed')
|
||||
})
|
||||
t.notOk(broker.closed)
|
||||
broker.close()
|
||||
t.ok(broker.closed)
|
||||
broker.close()
|
||||
t.ok(broker.closed)
|
||||
})
|
||||
|
||||
test('Test backpressure aedes published function', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
let publishCount = 10
|
||||
let count = 0
|
||||
|
||||
const broker = aedes({
|
||||
published: function (packet, client, done) {
|
||||
if (client) {
|
||||
count++
|
||||
setTimeout(() => {
|
||||
publisher.end()
|
||||
done()
|
||||
})
|
||||
} else { done() }
|
||||
}
|
||||
})
|
||||
|
||||
const mqtt = require('mqtt')
|
||||
const server = require('net').createServer(broker.handle)
|
||||
let publisher
|
||||
|
||||
server.listen(0, function () {
|
||||
const port = server.address().port
|
||||
publisher = mqtt.connect({ port, host: 'localhost', clean: true, keepalive: 30 })
|
||||
|
||||
function next () {
|
||||
if (--publishCount > 0) { process.nextTick(publish) }
|
||||
}
|
||||
|
||||
function publish () {
|
||||
publisher.publish('test', 'payload', next)
|
||||
}
|
||||
|
||||
publisher.on('connect', publish)
|
||||
publisher.on('end', function () {
|
||||
t.ok(count > publishCount)
|
||||
t.equal(publishCount, 0)
|
||||
broker.close()
|
||||
server.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('clear closed clients when the same clientId is managed by another broker', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const clientId = 'closed-client'
|
||||
const aedesBroker = aedes()
|
||||
|
||||
// simulate a closed client on the broker
|
||||
aedesBroker.clients[clientId] = { closed: true, broker: aedesBroker }
|
||||
aedesBroker.connectedClients = 1
|
||||
|
||||
// simulate the creation of the same client on another broker of the cluster
|
||||
aedesBroker.publish({ topic: '$SYS/anotherbroker/new/clients', payload: clientId }, () => {
|
||||
t.equal(aedesBroker.clients[clientId], undefined) // check that the closed client was removed
|
||||
t.equal(aedesBroker.connectedClients, 0)
|
||||
})
|
||||
|
||||
t.teardown(aedesBroker.close.bind(aedesBroker))
|
||||
})
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
'use strict'
|
||||
|
||||
const duplexify = require('duplexify')
|
||||
const mqtt = require('mqtt-connection')
|
||||
const { through } = require('../lib/utils')
|
||||
const util = require('util')
|
||||
const aedes = require('../')
|
||||
|
||||
const parseStream = mqtt.parseStream
|
||||
const generateStream = mqtt.generateStream
|
||||
let clients = 0
|
||||
|
||||
function setup (broker) {
|
||||
const inStream = generateStream()
|
||||
const outStream = parseStream()
|
||||
const conn = duplexify(outStream, inStream)
|
||||
|
||||
broker = broker || aedes()
|
||||
|
||||
return {
|
||||
client: broker.handle(conn),
|
||||
conn,
|
||||
inStream,
|
||||
outStream,
|
||||
broker
|
||||
}
|
||||
}
|
||||
|
||||
function connect (s, opts, connected) {
|
||||
s = Object.create(s)
|
||||
s.outStream = s.outStream.pipe(through(filter))
|
||||
|
||||
opts = opts || {}
|
||||
|
||||
opts.cmd = 'connect'
|
||||
opts.protocolId = opts.protocolId || 'MQTT'
|
||||
opts.protocolVersion = opts.protocolVersion || 4
|
||||
opts.clean = !!opts.clean
|
||||
opts.clientId = opts.clientId || 'my-client-' + clients++
|
||||
opts.keepalive = opts.keepalive || 0
|
||||
|
||||
s.inStream.write(opts)
|
||||
|
||||
return s
|
||||
|
||||
function filter (packet, enc, cb) {
|
||||
if (packet.cmd !== 'publish') {
|
||||
delete packet.topic
|
||||
delete packet.payload
|
||||
}
|
||||
|
||||
// using setImmediate to wait for connected to be fired
|
||||
// setup also needs to return first
|
||||
if (packet.cmd !== 'connack') {
|
||||
setImmediate(this.push.bind(this, packet))
|
||||
} else if (connected && packet.returnCode === 0) {
|
||||
setImmediate(connected, packet)
|
||||
}
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
function noError (s, t) {
|
||||
s.broker.on('clientError', function (client, err) {
|
||||
if (err) throw err
|
||||
t.notOk(err, 'must not error')
|
||||
})
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
function subscribe (t, subscriber, topic, qos, done) {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'subscribe',
|
||||
messageId: 24,
|
||||
subscriptions: [{
|
||||
topic,
|
||||
qos
|
||||
}]
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'suback')
|
||||
t.same(packet.granted, [qos])
|
||||
t.equal(packet.messageId, 24)
|
||||
|
||||
if (done) {
|
||||
done(null, packet)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// subs: [{topic:, qos:}]
|
||||
function subscribeMultiple (t, subscriber, subs, expectedGranted, done) {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'subscribe',
|
||||
messageId: 24,
|
||||
subscriptions: subs
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'suback')
|
||||
t.same(packet.granted, expectedGranted)
|
||||
t.equal(packet.messageId, 24)
|
||||
|
||||
if (done) {
|
||||
done(null, packet)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setup,
|
||||
connect,
|
||||
noError,
|
||||
subscribe,
|
||||
subscribeMultiple,
|
||||
delay: util.promisify(setTimeout)
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const eos = require('end-of-stream')
|
||||
const Faketimers = require('@sinonjs/fake-timers')
|
||||
const { setup, connect, noError } = require('./helper')
|
||||
const aedes = require('../')
|
||||
|
||||
test('supports pingreq/pingresp', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = noError(connect(setup()))
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('keepaliveTimeout', function (client) {
|
||||
t.fail('keep alive should not timeout')
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'pingreq'
|
||||
})
|
||||
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.equal(packet.cmd, 'pingresp', 'the response is a pingresp')
|
||||
})
|
||||
})
|
||||
|
||||
test('supports keep alive disconnections', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const clock = Faketimers.install()
|
||||
const s = connect(setup(), { keepalive: 1 })
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('keepaliveTimeout', function (client) {
|
||||
t.pass('keep alive timeout')
|
||||
})
|
||||
eos(s.conn, function () {
|
||||
t.pass('waits 1 and a half the keepalive timeout')
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
clock.uninstall()
|
||||
}, 1.5)
|
||||
clock.tick(1.5)
|
||||
})
|
||||
|
||||
test('supports keep alive disconnections after a pingreq', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const clock = Faketimers.install()
|
||||
const s = connect(setup(), { keepalive: 1 })
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
eos(s.conn, function () {
|
||||
t.pass('waits 1 and a half the keepalive timeout')
|
||||
})
|
||||
s.broker.on('keepaliveTimeout', function (client) {
|
||||
t.pass('keep alive timeout')
|
||||
})
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.equal(packet.cmd, 'pingresp', 'the response is a pingresp')
|
||||
})
|
||||
setTimeout(() => {
|
||||
s.inStream.write({
|
||||
cmd: 'pingreq'
|
||||
})
|
||||
clock.uninstall()
|
||||
}, 1)
|
||||
clock.tick(3)
|
||||
})
|
||||
|
||||
test('disconnect if a connect does not arrive in time', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const clock = Faketimers.install()
|
||||
const s = setup(aedes({
|
||||
connectTimeout: 500
|
||||
}))
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.client.on('error', function (err) {
|
||||
t.equal(err.message, 'connect did not arrive in time')
|
||||
})
|
||||
eos(s.conn, function () {
|
||||
t.pass('waits waitConnectTimeout before ending')
|
||||
})
|
||||
setTimeout(() => {
|
||||
clock.uninstall()
|
||||
}, 1000)
|
||||
clock.tick(1000)
|
||||
})
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { setup, connect, subscribe, noError } = require('./helper')
|
||||
const aedes = require('../')
|
||||
|
||||
test('count connected clients', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
t.equal(broker.connectedClients, 0, 'no connected clients')
|
||||
|
||||
connect(setup(broker), {
|
||||
}, function () {
|
||||
t.equal(broker.connectedClients, 1, 'one connected clients')
|
||||
|
||||
const last = connect(setup(broker), {
|
||||
}, function () {
|
||||
t.equal(broker.connectedClients, 2, 'two connected clients')
|
||||
|
||||
last.conn.destroy()
|
||||
|
||||
// needed because destroy() will do the trick before
|
||||
// the next tick
|
||||
setImmediate(function () {
|
||||
t.equal(broker.connectedClients, 1, 'one connected clients')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('call published method', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.published = function (packet, client, done) {
|
||||
t.equal(packet.topic, 'hello', 'topic matches')
|
||||
t.equal(packet.payload.toString(), 'world', 'payload matches')
|
||||
t.equal(client, null, 'no client')
|
||||
done()
|
||||
}
|
||||
|
||||
broker.publish({
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world')
|
||||
}, function (err) {
|
||||
t.error(err, 'no error')
|
||||
})
|
||||
})
|
||||
|
||||
test('call published method with client', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.published = function (packet, client, done) {
|
||||
// for internal messages, client will be null
|
||||
if (client) {
|
||||
t.equal(packet.topic, 'hello', 'topic matches')
|
||||
t.equal(packet.payload.toString(), 'world', 'payload matches')
|
||||
t.equal(packet.qos, 1)
|
||||
t.equal(packet.messageId, 42)
|
||||
done()
|
||||
}
|
||||
}
|
||||
|
||||
const s = connect(setup(broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
})
|
||||
|
||||
test('emit publish event with client - QoS 0', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.on('publish', function (packet, client) {
|
||||
// for internal messages, client will be null
|
||||
if (client) {
|
||||
t.equal(packet.qos, 0)
|
||||
t.equal(packet.topic, 'hello', 'topic matches')
|
||||
t.equal(packet.payload.toString(), 'world', 'payload matches')
|
||||
}
|
||||
})
|
||||
|
||||
const s = connect(setup(broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0
|
||||
})
|
||||
})
|
||||
|
||||
test('emit publish event with client - QoS 1', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.on('publish', function (packet, client) {
|
||||
// for internal messages, client will be null
|
||||
if (client) {
|
||||
t.equal(packet.qos, 1)
|
||||
t.equal(packet.messageId, 42)
|
||||
t.equal(packet.topic, 'hello', 'topic matches')
|
||||
t.equal(packet.payload.toString(), 'world', 'payload matches')
|
||||
}
|
||||
})
|
||||
|
||||
const s = connect(setup(broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
})
|
||||
|
||||
test('emit subscribe event', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = connect(setup(broker), { clientId: 'abcde' })
|
||||
|
||||
broker.on('subscribe', function (subscriptions, client) {
|
||||
t.same(subscriptions, [{
|
||||
topic: 'hello',
|
||||
qos: 0
|
||||
}], 'topic matches')
|
||||
t.equal(client.id, 'abcde', 'client matches')
|
||||
})
|
||||
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
t.pass('subscribe completed')
|
||||
})
|
||||
})
|
||||
|
||||
test('emit subscribe event if unrecognized params in subscribe packet structure', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = noError(connect(setup(broker)))
|
||||
const subs = [{ topic: 'hello', qos: 0 }]
|
||||
|
||||
broker.on('subscribe', function (subscriptions, client) {
|
||||
t.equal(subscriptions, subs)
|
||||
t.same(client, s.client)
|
||||
})
|
||||
|
||||
s.client.subscribe({
|
||||
subscriptions: subs,
|
||||
restore: true
|
||||
}, function (err) {
|
||||
t.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
test('emit unsubscribe event', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = connect(setup(broker), { clean: true, clientId: 'abcde' })
|
||||
|
||||
broker.on('unsubscribe', function (unsubscriptions, client) {
|
||||
t.same(unsubscriptions, [
|
||||
'hello'
|
||||
], 'unsubscription matches')
|
||||
t.equal(client.id, 'abcde', 'client matches')
|
||||
})
|
||||
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
s.inStream.write({
|
||||
cmd: 'unsubscribe',
|
||||
messageId: 43,
|
||||
unsubscriptions: ['hello']
|
||||
})
|
||||
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.pass('subscribe completed')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('emit unsubscribe event if unrecognized params in unsubscribe packet structure', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = noError(connect(setup(broker)))
|
||||
const unsubs = [{ topic: 'hello', qos: 0 }]
|
||||
|
||||
broker.on('unsubscribe', function (unsubscriptions, client) {
|
||||
t.equal(unsubscriptions, unsubs)
|
||||
t.same(client, s.client)
|
||||
})
|
||||
|
||||
s.client.unsubscribe({
|
||||
unsubscriptions: unsubs,
|
||||
close: true
|
||||
}, function (err) {
|
||||
t.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
test('dont emit unsubscribe event on client close', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = noError(connect(setup(broker), { clientId: 'abcde' }), t)
|
||||
|
||||
broker.on('unsubscribe', function (unsubscriptions, client) {
|
||||
t.error('unsubscribe should not be emitted')
|
||||
})
|
||||
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
s.inStream.end({
|
||||
cmd: 'disconnect'
|
||||
})
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.pass('unsubscribe completed')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('emit clientDisconnect event', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.on('clientDisconnect', function (client) {
|
||||
t.equal(client.id, 'abcde', 'client matches')
|
||||
})
|
||||
|
||||
const s = noError(connect(setup(broker), { clientId: 'abcde' }), t)
|
||||
|
||||
s.inStream.end({
|
||||
cmd: 'disconnect'
|
||||
})
|
||||
s.outStream.resume()
|
||||
})
|
||||
|
||||
test('emits client', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.on('client', function (client) {
|
||||
t.equal(client.id, 'abcde', 'clientId matches')
|
||||
})
|
||||
|
||||
connect(setup(broker), {
|
||||
clientId: 'abcde'
|
||||
})
|
||||
})
|
||||
|
||||
test('get aedes version', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
t.equal(broker.version, require('../package.json').version)
|
||||
})
|
||||
|
||||
test('connect and connackSent event', { timeout: 50 }, function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const s = setup()
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const clientId = 'my-client'
|
||||
|
||||
s.broker.on('connackSent', function (packet, client) {
|
||||
t.equal(packet.returnCode, 0)
|
||||
t.equal(client.id, clientId, 'connackSent event and clientId matches')
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'connect',
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 4,
|
||||
clean: true,
|
||||
clientId,
|
||||
keepalive: 0
|
||||
})
|
||||
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'connack',
|
||||
returnCode: 0,
|
||||
length: 2,
|
||||
qos: 0,
|
||||
retain: false,
|
||||
dup: false,
|
||||
topic: null,
|
||||
payload: null,
|
||||
sessionPresent: false
|
||||
}, 'successful connack')
|
||||
})
|
||||
})
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const EventEmitter = require('events')
|
||||
const mqtt = require('mqtt')
|
||||
const net = require('net')
|
||||
const Faketimers = require('@sinonjs/fake-timers')
|
||||
const aedes = require('../')
|
||||
|
||||
test('connect 500 concurrent clients', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const evt = new EventEmitter()
|
||||
const broker = aedes()
|
||||
const server = net.createServer(broker.handle)
|
||||
const total = 500
|
||||
|
||||
server.listen(0, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
const clock = Faketimers.createClock()
|
||||
t.teardown(clock.reset.bind(clock))
|
||||
|
||||
const port = server.address().port
|
||||
|
||||
let connected = 0
|
||||
const clients = []
|
||||
clock.setTimeout(function () {
|
||||
t.equal(clients.length, total)
|
||||
t.equal(connected, total)
|
||||
while (clients.length) {
|
||||
clients.shift().end()
|
||||
}
|
||||
}, total)
|
||||
|
||||
evt.on('finish', function () {
|
||||
if (clients.length === 0) {
|
||||
broker.close()
|
||||
server.close()
|
||||
}
|
||||
})
|
||||
|
||||
for (let i = 0; i < total; i++) {
|
||||
clients[i] = mqtt.connect({
|
||||
port,
|
||||
keepalive: 0,
|
||||
reconnectPeriod: 100
|
||||
}).on('connect', function () {
|
||||
connected++
|
||||
if ((connected % (total / 10)) === 0) {
|
||||
console.log('connected', connected)
|
||||
}
|
||||
clock.tick(1)
|
||||
}).on('close', function () {
|
||||
evt.emit('finish')
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('do not block after a subscription', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const evt = new EventEmitter()
|
||||
const broker = aedes()
|
||||
const server = net.createServer(broker.handle)
|
||||
const total = 10000
|
||||
let sent = 0
|
||||
let received = 0
|
||||
|
||||
server.listen(0, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
const clock = Faketimers.createClock()
|
||||
t.teardown(clock.reset.bind(clock))
|
||||
|
||||
const clockId = clock.setTimeout(finish, total)
|
||||
|
||||
const port = server.address().port
|
||||
|
||||
const publisher = mqtt.connect({
|
||||
port,
|
||||
keepalive: 0
|
||||
}).on('error', function (err) {
|
||||
clock.clearTimeout(clockId)
|
||||
t.fail(err)
|
||||
})
|
||||
|
||||
let subscriber
|
||||
|
||||
function immediatePublish () {
|
||||
setImmediate(publish)
|
||||
}
|
||||
|
||||
function publish () {
|
||||
if (sent === total) {
|
||||
publisher.end()
|
||||
} else {
|
||||
sent++
|
||||
publisher.publish('test', 'payload', immediatePublish)
|
||||
}
|
||||
}
|
||||
|
||||
function startSubscriber () {
|
||||
subscriber = mqtt.connect({
|
||||
port,
|
||||
keepalive: 0
|
||||
}).on('error', function (err) {
|
||||
clock.clearTimeout(clockId)
|
||||
t.fail(err)
|
||||
})
|
||||
|
||||
subscriber.subscribe('test', publish)
|
||||
|
||||
subscriber.on('message', function () {
|
||||
if (received % (total / 10) === 0) {
|
||||
console.log('sent / received', sent, received)
|
||||
}
|
||||
received++
|
||||
clock.tick(1)
|
||||
})
|
||||
subscriber.on('close', function () {
|
||||
evt.emit('finish')
|
||||
})
|
||||
}
|
||||
|
||||
publisher.on('connect', startSubscriber)
|
||||
publisher.on('close', function () {
|
||||
evt.emit('finish')
|
||||
})
|
||||
evt.on('finish', function () {
|
||||
if (publisher.connected || subscriber.connected) { return }
|
||||
broker.close()
|
||||
server.close()
|
||||
t.equal(total, sent, 'messages sent')
|
||||
t.equal(total, received, 'messages received')
|
||||
})
|
||||
function finish () {
|
||||
subscriber.end()
|
||||
publisher.end()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('do not block with overlapping subscription', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const evt = new EventEmitter()
|
||||
const broker = aedes({ concurrency: 15 })
|
||||
const server = net.createServer(broker.handle)
|
||||
const total = 10000
|
||||
let sent = 0
|
||||
let received = 0
|
||||
|
||||
server.listen(0, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
const clock = Faketimers.createClock()
|
||||
t.teardown(clock.reset.bind(clock))
|
||||
|
||||
const clockId = clock.setTimeout(finish, total)
|
||||
|
||||
const port = server.address().port
|
||||
|
||||
const publisher = mqtt.connect({
|
||||
port,
|
||||
keepalive: 0
|
||||
}).on('error', function (err) {
|
||||
clock.clearTimeout(clockId)
|
||||
t.fail(err)
|
||||
})
|
||||
|
||||
let subscriber
|
||||
|
||||
function immediatePublish (e) {
|
||||
setImmediate(publish)
|
||||
}
|
||||
|
||||
function publish () {
|
||||
if (sent === total) {
|
||||
publisher.end()
|
||||
} else {
|
||||
sent++
|
||||
publisher.publish('test', 'payload', immediatePublish)
|
||||
}
|
||||
}
|
||||
|
||||
function startSubscriber () {
|
||||
subscriber = mqtt.connect({
|
||||
port,
|
||||
keepalive: 0
|
||||
}).on('error', function (err) {
|
||||
clock.clearTimeout(clockId)
|
||||
t.fail(err)
|
||||
})
|
||||
|
||||
subscriber.subscribe('#', function () {
|
||||
subscriber.subscribe('test', function () {
|
||||
immediatePublish()
|
||||
})
|
||||
})
|
||||
|
||||
subscriber.on('message', function () {
|
||||
if (received % (total / 10) === 0) {
|
||||
console.log('sent / received', sent, received)
|
||||
}
|
||||
received++
|
||||
clock.tick(1)
|
||||
})
|
||||
subscriber.on('close', function () {
|
||||
evt.emit('finish')
|
||||
})
|
||||
}
|
||||
|
||||
publisher.on('connect', startSubscriber)
|
||||
publisher.on('close', function () {
|
||||
evt.emit('finish')
|
||||
})
|
||||
evt.on('finish', function () {
|
||||
if (publisher.connected || subscriber.connected) { return }
|
||||
broker.close()
|
||||
server.close()
|
||||
t.equal(total, sent, 'messages sent')
|
||||
t.equal(total, received, 'messages received')
|
||||
})
|
||||
function finish () {
|
||||
subscriber.end()
|
||||
publisher.end()
|
||||
}
|
||||
})
|
||||
})
|
||||
+880
@@ -0,0 +1,880 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const concat = require('concat-stream')
|
||||
const { setup, connect, subscribe } = require('./helper')
|
||||
const Faketimers = require('@sinonjs/fake-timers')
|
||||
const aedes = require('../')
|
||||
|
||||
test('publish QoS 1', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const expected = {
|
||||
cmd: 'puback',
|
||||
messageId: 42,
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 2,
|
||||
retain: false
|
||||
}
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
|
||||
test('publish QoS 1 throws error', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.persistence.subscriptionsByTopic = function (packet, done) {
|
||||
return done(new Error('Throws error'))
|
||||
}
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
|
||||
s.broker.on('error', function (err) {
|
||||
t.equal('Throws error', err.message, 'Throws error')
|
||||
})
|
||||
})
|
||||
|
||||
test('publish QoS 1 throws error on write', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('client', function (client) {
|
||||
client.connected = false
|
||||
client.connecting = false
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
})
|
||||
|
||||
s.broker.on('clientError', function (client, err) {
|
||||
t.equal(err.message, 'connection closed', 'throws error')
|
||||
})
|
||||
})
|
||||
|
||||
test('publish QoS 1 and check offline queue', function (t) {
|
||||
t.plan(13)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker), { clean: false })
|
||||
const subscriberClient = {
|
||||
id: 'abcde'
|
||||
}
|
||||
const subscriber = connect(setup(broker), { clean: false, clientId: subscriberClient.id })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
qos: 1,
|
||||
dup: false,
|
||||
retain: false
|
||||
}
|
||||
const expectedAck = {
|
||||
cmd: 'puback',
|
||||
retain: false,
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 2,
|
||||
messageId: 10
|
||||
}
|
||||
const sent = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 10,
|
||||
retain: false,
|
||||
dup: false
|
||||
}
|
||||
const queue = []
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
publisher.outStream.on('data', function (packet) {
|
||||
t.same(packet, expectedAck, 'ack packet must patch')
|
||||
})
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
queue.push(packet)
|
||||
delete packet.payload
|
||||
delete packet.length
|
||||
t.not(packet.messageId, undefined, 'messageId is assigned a value')
|
||||
t.not(packet.messageId, 10, 'messageId should be unique')
|
||||
expected.messageId = packet.messageId
|
||||
t.same(packet, expected, 'publish packet must patch')
|
||||
if (queue.length === 2) {
|
||||
setImmediate(() => {
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
broker.persistence.outgoingClearMessageId(subscriberClient, queue[i], function (_, origPacket) {
|
||||
if (origPacket) {
|
||||
delete origPacket.brokerId
|
||||
delete origPacket.brokerCounter
|
||||
delete origPacket.payload
|
||||
delete origPacket.messageId
|
||||
delete sent.payload
|
||||
delete sent.messageId
|
||||
t.same(origPacket, sent, 'origPacket must match')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
publisher.inStream.write(sent)
|
||||
sent.payload = 'world2world'
|
||||
publisher.inStream.write(sent)
|
||||
})
|
||||
})
|
||||
|
||||
test('publish QoS 1 and empty offline queue', function (t) {
|
||||
t.plan(13)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker), { clean: false })
|
||||
const subscriberClient = {
|
||||
id: 'abcde'
|
||||
}
|
||||
const subscriber = connect(setup(broker), { clean: false, clientId: subscriberClient.id })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
qos: 1,
|
||||
dup: false,
|
||||
retain: false
|
||||
}
|
||||
const expectedAck = {
|
||||
cmd: 'puback',
|
||||
retain: false,
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 2,
|
||||
messageId: 10
|
||||
}
|
||||
const sent = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 10,
|
||||
retain: false,
|
||||
dup: false
|
||||
}
|
||||
const queue = []
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
publisher.outStream.on('data', function (packet) {
|
||||
t.same(packet, expectedAck, 'ack packet must patch')
|
||||
})
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
queue.push(packet)
|
||||
delete packet.payload
|
||||
delete packet.length
|
||||
t.not(packet.messageId, undefined, 'messageId is assigned a value')
|
||||
t.not(packet.messageId, 10, 'messageId should be unique')
|
||||
expected.messageId = packet.messageId
|
||||
t.same(packet, expected, 'publish packet must patch')
|
||||
if (queue.length === 2) {
|
||||
setImmediate(() => {
|
||||
broker.clients[subscriberClient.id].emptyOutgoingQueue(function () {
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
broker.persistence.outgoingClearMessageId(subscriberClient, queue[i], function (_, origPacket) {
|
||||
t.equal(!!origPacket, false, 'Packet has been removed')
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
publisher.inStream.write(sent)
|
||||
sent.payload = 'world2world'
|
||||
publisher.inStream.write(sent)
|
||||
})
|
||||
})
|
||||
|
||||
test('subscribe QoS 1', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'puback',
|
||||
messageId: packet.messageId
|
||||
})
|
||||
t.not(packet.messageId, 42, 'messageId must differ')
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('subscribe QoS 0, but publish QoS 1', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 12,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('restore QoS 1 subscriptions not clean', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
|
||||
t.equal(connect.sessionPresent, true, 'session present is set to true')
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
})
|
||||
|
||||
publisher.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'puback',
|
||||
messageId: packet.messageId
|
||||
})
|
||||
t.not(packet.messageId, 42, 'messageId must differ')
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('restore multiple QoS 1 subscriptions not clean w/ authorizeSubscribe', function (t) {
|
||||
t.plan(11)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'foo',
|
||||
payload: Buffer.from('bar'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 10,
|
||||
retain: false
|
||||
}
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscribe(t, subscriber, 'foo', 1, function () {
|
||||
subscriber.inStream.end()
|
||||
broker.authorizeSubscribe = function (client, sub, done) {
|
||||
done(null, sub.topic === 'hello' ? 123 : sub)
|
||||
}
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
|
||||
t.equal(connect.sessionPresent, true, 'session present is set to true')
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'foo',
|
||||
payload: 'bar',
|
||||
qos: 1,
|
||||
messageId: 48
|
||||
})
|
||||
})
|
||||
publisher.outStream.on('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
})
|
||||
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'puback',
|
||||
messageId: packet.messageId
|
||||
})
|
||||
t.not(packet.messageId, 48, 'messageId must differ')
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('remove stored subscriptions if connected with clean=true', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
subscriber = connect(setup(broker), { clean: true, clientId: 'abcde' }, function (packet) {
|
||||
t.equal(packet.sessionPresent, false, 'session present is set to false')
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
|
||||
subscriber.inStream.end()
|
||||
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
|
||||
t.equal(connect.sessionPresent, false, 'session present is set to false')
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 43
|
||||
})
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('publish received')
|
||||
})
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('publish received')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('resend publish on non-clean reconnect QoS 1', function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const opts = { clean: false, clientId: 'abcde' }
|
||||
let subscriber = connect(setup(broker), opts)
|
||||
const subscriberClient = {
|
||||
id: opts.clientId
|
||||
}
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
publisher.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
|
||||
subscriber = connect(setup(broker), opts)
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'puback',
|
||||
messageId: packet.messageId
|
||||
})
|
||||
t.not(packet.messageId, 42, 'messageId must differ')
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
setImmediate(() => {
|
||||
const stream = broker.persistence.outgoingStream(subscriberClient)
|
||||
stream.pipe(concat(function (list) {
|
||||
t.equal(list.length, 1, 'should remain one item in queue')
|
||||
t.same(list[0].payload, Buffer.from('world world'), 'packet must match')
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('resend many publish on non-clean reconnect QoS 1', function (t) {
|
||||
t.plan(4)
|
||||
const broker = aedes()
|
||||
const clock = Faketimers.createClock()
|
||||
|
||||
t.teardown(() => {
|
||||
broker.close.bind(broker)
|
||||
clock.reset.bind(clock)
|
||||
})
|
||||
|
||||
const opts = { clean: false, clientId: 'abcde' }
|
||||
let subscriber = connect(setup(broker), opts)
|
||||
const publisher = connect(setup(broker))
|
||||
const { through } = require('../lib/utils')
|
||||
const total = through().writableHighWaterMark * 2
|
||||
|
||||
let received = 0
|
||||
clock.setTimeout(() => {
|
||||
broker.close()
|
||||
t.equal(received, total)
|
||||
}, total)
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
for (let sent = 0; sent < total; sent++) {
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'message-' + sent,
|
||||
qos: 1,
|
||||
messageId: 42 + sent
|
||||
})
|
||||
}
|
||||
publisher.outStream.once('data', function (packet) {
|
||||
subscriber = connect(setup(broker), opts)
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'puback',
|
||||
messageId: packet.messageId
|
||||
})
|
||||
received++
|
||||
clock.tick(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('do not resend QoS 1 packets at each reconnect', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
|
||||
publisher.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
subscriber.inStream.end({
|
||||
cmd: 'puback',
|
||||
messageId: packet.messageId
|
||||
})
|
||||
|
||||
t.not(packet.messageId, 42, 'messageId must differ')
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
|
||||
const subscriber2 = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
|
||||
subscriber2.outStream.once('data', function (packet) {
|
||||
t.fail('this should never happen')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('do not resend QoS 1 packets if reconnect is clean', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
|
||||
publisher.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
|
||||
subscriber = connect(setup(broker), { clean: true, clientId: 'abcde' })
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('this should never happen')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('do not resend QoS 1 packets at reconnect if puback was received', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
|
||||
publisher.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
subscriber.inStream.end({
|
||||
cmd: 'puback',
|
||||
messageId: packet.messageId
|
||||
})
|
||||
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('this should never happen')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('remove stored subscriptions after unsubscribe', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'unsubscribe',
|
||||
messageId: 43,
|
||||
unsubscriptions: ['hello']
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'unsuback',
|
||||
messageId: 43,
|
||||
dup: false,
|
||||
length: 2,
|
||||
qos: 0,
|
||||
retain: false
|
||||
}, 'packet matches')
|
||||
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (packet) {
|
||||
t.equal(packet.sessionPresent, false, 'session present is set to false')
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 43
|
||||
}, function () {
|
||||
subscriber.inStream.end()
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('publish received')
|
||||
})
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('publish received')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('upgrade a QoS 0 subscription to QoS 1', function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
length: 14,
|
||||
retain: false,
|
||||
dup: false
|
||||
}
|
||||
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
subscribe(t, s, 'hello', 1, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.ok(packet.messageId, 'has messageId')
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet matches')
|
||||
})
|
||||
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('downgrade QoS 0 publish on QoS 1 subsciption', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
length: 12,
|
||||
retain: false,
|
||||
dup: false
|
||||
}
|
||||
|
||||
subscribe(t, s, 'hello', 1, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet matches')
|
||||
})
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 0
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('subscribe and publish QoS 1 in parallel', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
broker.on('clientError', function (client, err) {
|
||||
console.log(err.stack)
|
||||
// t.fail('no client error')
|
||||
})
|
||||
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
t.equal(packet.messageId, 42, 'messageId must match')
|
||||
s.outStream.on('data', function (packet) {
|
||||
if (packet.cmd === 'suback') {
|
||||
t.same(packet.granted, [1])
|
||||
t.equal(packet.messageId, 24)
|
||||
}
|
||||
if (packet.cmd === 'publish') {
|
||||
s.inStream.write({
|
||||
cmd: 'puback',
|
||||
messageId: packet.messageId
|
||||
})
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'subscribe',
|
||||
messageId: 24,
|
||||
subscriptions: [{
|
||||
topic: 'hello',
|
||||
qos: 1
|
||||
}]
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42
|
||||
})
|
||||
})
|
||||
+686
@@ -0,0 +1,686 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const concat = require('concat-stream')
|
||||
const { setup, connect, subscribe } = require('./helper')
|
||||
const aedes = require('../')
|
||||
|
||||
function publish (t, s, packet, done) {
|
||||
const msgId = packet.messageId
|
||||
|
||||
s.inStream.write(packet)
|
||||
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'pubrec',
|
||||
messageId: msgId,
|
||||
length: 2,
|
||||
dup: false,
|
||||
retain: false,
|
||||
qos: 0
|
||||
}, 'pubrec must match')
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'pubrel',
|
||||
messageId: msgId
|
||||
})
|
||||
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'pubcomp',
|
||||
messageId: msgId,
|
||||
length: 2,
|
||||
dup: false,
|
||||
retain: false,
|
||||
qos: 0
|
||||
}, 'pubcomp must match')
|
||||
|
||||
if (done) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function receive (t, subscriber, expected, done) {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.not(packet.messageId, expected.messageId, 'messageId must differ')
|
||||
|
||||
const msgId = packet.messageId
|
||||
delete packet.messageId
|
||||
delete expected.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
|
||||
subscriber.inStream.write({
|
||||
cmd: 'pubrec',
|
||||
messageId: msgId
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'pubcomp',
|
||||
messageId: msgId
|
||||
})
|
||||
t.same(packet, {
|
||||
cmd: 'pubrel',
|
||||
messageId: msgId,
|
||||
length: 2,
|
||||
qos: 1,
|
||||
retain: false,
|
||||
dup: false
|
||||
}, 'pubrel must match')
|
||||
|
||||
if (done) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
test('publish QoS 2', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const packet = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 2,
|
||||
messageId: 42
|
||||
}
|
||||
publish(t, s, packet)
|
||||
})
|
||||
|
||||
test('subscribe QoS 2', function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const toPublish = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
messageId: 42,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 2, function () {
|
||||
publish(t, publisher, toPublish)
|
||||
|
||||
receive(t, subscriber, toPublish)
|
||||
})
|
||||
})
|
||||
|
||||
test('publish QoS 2 throws error on write', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('client', function (client) {
|
||||
client.connected = false
|
||||
client.connecting = false
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 2,
|
||||
messageId: 42
|
||||
})
|
||||
})
|
||||
|
||||
s.broker.on('clientError', function (client, err) {
|
||||
t.equal(err.message, 'connection closed', 'throws error')
|
||||
})
|
||||
})
|
||||
|
||||
test('pubrec handler calls done when outgoingUpdate fails (clean=false)', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup(), { clean: false })
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const handle = require('../lib/handlers/pubrec.js')
|
||||
|
||||
s.broker.persistence.outgoingUpdate = function (client, pubrel, done) {
|
||||
done(Error('throws error'))
|
||||
}
|
||||
|
||||
handle(s.client, { messageId: 42 }, function done () {
|
||||
t.pass('calls done on error')
|
||||
})
|
||||
})
|
||||
|
||||
test('client.publish with clean=true subscribption QoS 2', function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const toPublish = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
messageId: 42,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
let brokerClient = null
|
||||
|
||||
broker.on('client', function (client) {
|
||||
brokerClient = client
|
||||
|
||||
brokerClient.on('error', function (err) {
|
||||
t.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
const subscriber = connect(setup(broker), { clean: true })
|
||||
|
||||
subscribe(t, subscriber, 'hello', 2, function () {
|
||||
t.pass('subscribed')
|
||||
receive(t, subscriber, toPublish)
|
||||
brokerClient.publish(toPublish, function (err) {
|
||||
t.error(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('call published method with client with QoS 2', function (t) {
|
||||
t.plan(9)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const toPublish = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
messageId: 42,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
broker.published = function (packet, client, cb) {
|
||||
// Client is null for all server publishes
|
||||
if (packet.topic.split('/')[0] !== '$SYS') {
|
||||
t.ok(client, 'client must be passed to published method')
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 2, function () {
|
||||
publish(t, publisher, toPublish)
|
||||
|
||||
receive(t, subscriber, toPublish)
|
||||
})
|
||||
})
|
||||
|
||||
;[true, false].forEach(function (cleanSession) {
|
||||
test(`authorized forward publish packets in QoS 2 [clean=${cleanSession}]`, function (t) {
|
||||
t.plan(9)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const opts = { clean: cleanSession }
|
||||
const publisher = connect(setup(broker), { clientId: 'my-client-xyz-8' })
|
||||
const subscriber = connect(setup(broker), { ...opts, clientId: 'abcde' })
|
||||
const forwarded = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
retain: false,
|
||||
dup: false,
|
||||
messageId: undefined,
|
||||
clientId: 'my-client-xyz-8'
|
||||
}
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
retain: false,
|
||||
length: 14,
|
||||
dup: false
|
||||
}
|
||||
broker.authorizeForward = function (client, packet) {
|
||||
forwarded.brokerId = broker.id
|
||||
forwarded.brokerCounter = broker.counter
|
||||
delete packet.nl
|
||||
t.same(packet, forwarded, 'forwarded packet must match')
|
||||
return packet
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 2, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.not(packet.messageId, 42)
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
|
||||
publish(t, publisher, {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
retain: false,
|
||||
messageId: 42,
|
||||
dup: false
|
||||
}, function () {
|
||||
const stream = broker.persistence.outgoingStream({ id: 'abcde' })
|
||||
stream.pipe(concat(function (list) {
|
||||
if (cleanSession) {
|
||||
t.equal(list.length, 0, 'should have empty item in queue')
|
||||
} else {
|
||||
t.equal(list.length, 1, 'should have one item in queue')
|
||||
}
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
;[true, false].forEach(function (cleanSession) {
|
||||
test(`unauthorized forward publish packets in QoS 2 [clean=${cleanSession}]`, function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const opts = { clean: cleanSession }
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker), { ...opts, clientId: 'abcde' })
|
||||
|
||||
broker.authorizeForward = function (client, packet) {
|
||||
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 2, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('should not receive any packets')
|
||||
})
|
||||
|
||||
publish(t, publisher, {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
retain: false,
|
||||
messageId: 42,
|
||||
dup: false
|
||||
}, function () {
|
||||
const stream = broker.persistence.outgoingStream({ id: 'abcde' })
|
||||
stream.pipe(concat(function (list) {
|
||||
t.equal(list.length, 0, 'should empty in queue')
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('subscribe QoS 0, but publish QoS 2', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 12,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
|
||||
publish(t, publisher, {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
retain: false,
|
||||
messageId: 42,
|
||||
dup: false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('subscribe QoS 1, but publish QoS 2', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
|
||||
publish(t, publisher, {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
retain: false,
|
||||
messageId: 42,
|
||||
dup: false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('restore QoS 2 subscriptions not clean', function (t) {
|
||||
t.plan(9)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
dup: false,
|
||||
length: 14,
|
||||
messageId: 42,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 2, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
|
||||
t.equal(connect.sessionPresent, true, 'session present is set to true')
|
||||
publish(t, publisher, expected)
|
||||
})
|
||||
|
||||
receive(t, subscriber, expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('resend publish on non-clean reconnect QoS 2', function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const opts = { clean: false, clientId: 'abcde' }
|
||||
let subscriber = connect(setup(broker), opts)
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
dup: false,
|
||||
length: 14,
|
||||
messageId: 42,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 2, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
publish(t, publisher, expected, function () {
|
||||
subscriber = connect(setup(broker), opts)
|
||||
|
||||
receive(t, subscriber, expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('resend pubrel on non-clean reconnect QoS 2', function (t) {
|
||||
t.plan(9)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const opts = { clean: false, clientId: 'abcde' }
|
||||
let subscriber = connect(setup(broker), opts)
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
dup: false,
|
||||
length: 14,
|
||||
messageId: 42,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 2, function () {
|
||||
subscriber.inStream.end()
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
|
||||
publish(t, publisher, expected, function () {
|
||||
subscriber = connect(setup(broker), opts)
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.not(packet.messageId, expected.messageId, 'messageId must differ')
|
||||
|
||||
const msgId = packet.messageId
|
||||
delete packet.messageId
|
||||
delete expected.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
|
||||
subscriber.inStream.write({
|
||||
cmd: 'pubrec',
|
||||
messageId: msgId
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'pubrel',
|
||||
messageId: msgId,
|
||||
length: 2,
|
||||
qos: 1,
|
||||
retain: false,
|
||||
dup: false
|
||||
}, 'pubrel must match')
|
||||
|
||||
subscriber.inStream.end()
|
||||
|
||||
subscriber = connect(setup(broker), opts)
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, {
|
||||
cmd: 'pubrel',
|
||||
messageId: msgId,
|
||||
length: 2,
|
||||
qos: 1,
|
||||
retain: false,
|
||||
dup: false
|
||||
}, 'pubrel must match')
|
||||
|
||||
subscriber.inStream.write({
|
||||
cmd: 'pubcomp',
|
||||
messageId: msgId
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('publish after disconnection', function (t) {
|
||||
t.plan(10)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const toPublish = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
messageId: 42,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
const toPublish2 = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('worl2'),
|
||||
qos: 2,
|
||||
messageId: 43,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 2, function () {
|
||||
publish(t, publisher, toPublish)
|
||||
|
||||
receive(t, subscriber, toPublish, function () {
|
||||
publish(t, publisher, toPublish2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('multiple publish and store one', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const broker = aedes()
|
||||
|
||||
const sid = {
|
||||
id: 'abcde'
|
||||
}
|
||||
const s = connect(setup(broker), { clientId: sid.id })
|
||||
const toPublish = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 2,
|
||||
retain: false,
|
||||
dup: false,
|
||||
messageId: 42
|
||||
}
|
||||
|
||||
let count = 5
|
||||
while (count--) {
|
||||
s.inStream.write(toPublish)
|
||||
}
|
||||
let recvcnt = 0
|
||||
s.outStream.on('data', function (packet) {
|
||||
if (++recvcnt < 5) return
|
||||
broker.close(function () {
|
||||
broker.persistence.incomingGetPacket(sid, toPublish, function (err, origPacket) {
|
||||
delete origPacket.brokerId
|
||||
delete origPacket.brokerCounter
|
||||
t.same(origPacket, toPublish, 'packet must match')
|
||||
t.error(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('packet is written to stream after being stored', function (t) {
|
||||
const s = connect(setup())
|
||||
|
||||
const broker = s.broker
|
||||
|
||||
t.teardown(broker.close.bind(s.broker))
|
||||
|
||||
let packetStored = false
|
||||
|
||||
const fn = broker.persistence.incomingStorePacket.bind(broker.persistence)
|
||||
|
||||
s.broker.persistence.incomingStorePacket = function (client, packet, done) {
|
||||
packetStored = true
|
||||
t.pass('packet stored')
|
||||
fn(client, packet, done)
|
||||
}
|
||||
|
||||
const packet = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 2,
|
||||
messageId: 42
|
||||
}
|
||||
|
||||
publish(t, s, packet)
|
||||
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'pubrec', 'pubrec received')
|
||||
t.equal(packetStored, true, 'after packet store')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
test('not send pubrec when persistence fails to store packet', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const s = connect(setup())
|
||||
const broker = s.broker
|
||||
|
||||
t.teardown(broker.close.bind(s.broker))
|
||||
|
||||
s.broker.persistence.incomingStorePacket = function (client, packet, done) {
|
||||
t.pass('packet stored')
|
||||
done(new Error('store error'))
|
||||
}
|
||||
s.broker.on('clientError', function (client, err) {
|
||||
t.equal(err.message, 'store error')
|
||||
})
|
||||
|
||||
const packet = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 2,
|
||||
messageId: 42
|
||||
}
|
||||
|
||||
s.inStream.write(packet)
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.fail('should not have pubrec')
|
||||
})
|
||||
})
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { setup, connect } = require('./helper')
|
||||
|
||||
test('after an error, outstanding packets are discarded', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup(), {
|
||||
keepalive: 1000
|
||||
})
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
const packet = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world'
|
||||
}
|
||||
|
||||
s.broker.mq.on('hello', function (msg, cb) {
|
||||
t.pass('first msg received')
|
||||
s.inStream.destroy(new Error('something went wrong'))
|
||||
cb()
|
||||
setImmediate(() => {
|
||||
packet.topic = 'foo'
|
||||
s.inStream.write(packet)
|
||||
s.inStream.write(packet)
|
||||
})
|
||||
})
|
||||
s.broker.mq.on('foo', function (msg, cb) {
|
||||
t.fail('msg received')
|
||||
})
|
||||
s.inStream.write(packet)
|
||||
})
|
||||
+713
@@ -0,0 +1,713 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { through } = require('../lib/utils')
|
||||
const Faketimers = require('@sinonjs/fake-timers')
|
||||
const { setup, connect, subscribe, noError } = require('./helper')
|
||||
const aedes = require('../')
|
||||
|
||||
// [MQTT-3.3.1-9]
|
||||
test('live retain packets', function (t) {
|
||||
t.plan(5)
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
retain: false,
|
||||
dup: false,
|
||||
length: 12,
|
||||
qos: 0
|
||||
}
|
||||
|
||||
const s = noError(connect(setup()), t)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
subscribe(t, s, 'hello', 0, function () {
|
||||
s.outStream.on('data', function (packet) {
|
||||
t.same(packet, expected)
|
||||
})
|
||||
|
||||
s.broker.publish({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
retain: true,
|
||||
dup: false,
|
||||
length: 12,
|
||||
qos: 0
|
||||
}, function () {
|
||||
t.pass('publish finished')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('retain messages', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 12,
|
||||
retain: true
|
||||
}
|
||||
|
||||
broker.subscribe('hello', function (packet, cb) {
|
||||
cb()
|
||||
|
||||
// defer this or it will receive the message which
|
||||
// is being published
|
||||
setImmediate(function () {
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
publisher.inStream.write(expected)
|
||||
})
|
||||
|
||||
test('retain messages propagates through broker subscriptions', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
retain: true
|
||||
}
|
||||
|
||||
const subscriberFunc = function (packet, cb) {
|
||||
packet = Object.assign({}, packet)
|
||||
delete packet.brokerId
|
||||
delete packet.brokerCounter
|
||||
cb()
|
||||
setImmediate(function () {
|
||||
t.same(packet, expected, 'packet must not have been modified')
|
||||
})
|
||||
}
|
||||
|
||||
broker.subscribe('hello', subscriberFunc, function () {
|
||||
broker.publish(expected)
|
||||
})
|
||||
})
|
||||
|
||||
test('avoid wrong deduping of retain messages', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 12,
|
||||
retain: true
|
||||
}
|
||||
|
||||
broker.subscribe('hello', function (packet, cb) {
|
||||
cb()
|
||||
// subscribe and publish another topic
|
||||
subscribe(t, subscriber, 'hello2', 0, function () {
|
||||
cb()
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello2',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
publisher.inStream.write(expected)
|
||||
})
|
||||
|
||||
test('reconnected subscriber will not receive retained messages when QoS 0 and clean', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker), { clean: true })
|
||||
let subscriber = connect(setup(broker), { clean: true })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
retain: false,
|
||||
dup: false,
|
||||
length: 12
|
||||
}
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 0,
|
||||
retain: false
|
||||
})
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
subscriber.inStream.end()
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'foo',
|
||||
qos: 0,
|
||||
retain: true
|
||||
})
|
||||
subscriber = connect(setup(broker), { clean: true })
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
t.fail('should not received retain message')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('subscriber will not receive retained messages when QoS is 128', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const pubPacket = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
retain: true,
|
||||
messageId: 42
|
||||
}
|
||||
|
||||
broker.authorizeSubscribe = function (client, sub, callback) {
|
||||
if (sub.topic === pubPacket.topic) {
|
||||
callback(null, null)
|
||||
} else {
|
||||
callback(null, sub)
|
||||
}
|
||||
}
|
||||
|
||||
const publisher = connect(setup(broker), { clean: true })
|
||||
|
||||
publisher.inStream.write(pubPacket)
|
||||
|
||||
publisher.outStream.on('data', function (packet) {
|
||||
const subscriber = connect(setup(broker), { clean: true })
|
||||
subscribe(t, subscriber, pubPacket.topic, 128, function () {
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
t.fail('should not received retain message')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.3.1-6]
|
||||
test('new QoS 0 subscribers receive QoS 0 retained messages when clean', function (t) {
|
||||
t.plan(9)
|
||||
|
||||
const clock = Faketimers.createClock()
|
||||
const broker = aedes()
|
||||
t.teardown(function () {
|
||||
clock.reset()
|
||||
broker.close()
|
||||
})
|
||||
|
||||
const publisher = connect(setup(broker), { clean: true })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello/world',
|
||||
payload: Buffer.from('big big world'),
|
||||
qos: 0,
|
||||
retain: true,
|
||||
dup: false,
|
||||
length: 26
|
||||
}
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello/world',
|
||||
payload: 'big big world',
|
||||
qos: 0,
|
||||
retain: true
|
||||
})
|
||||
const subscriber1 = connect(setup(broker), { clean: true })
|
||||
subscribe(t, subscriber1, 'hello/world', 0, function () {
|
||||
subscriber1.outStream.on('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
clock.tick(100)
|
||||
})
|
||||
})
|
||||
const subscriber2 = connect(setup(broker), { clean: true })
|
||||
subscribe(t, subscriber2, 'hello/+', 0, function () {
|
||||
subscriber2.outStream.on('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
clock.tick(100)
|
||||
})
|
||||
})
|
||||
|
||||
clock.setTimeout(() => {
|
||||
t.equal(broker.counter, 9)
|
||||
}, 200)
|
||||
})
|
||||
|
||||
// [MQTT-3.3.1-5]
|
||||
test('new QoS 0 subscribers receive downgraded QoS 1 retained messages when clean', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const broker = aedes()
|
||||
|
||||
const publisher = connect(setup(broker), { clean: true })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
retain: true,
|
||||
dup: false,
|
||||
length: 12
|
||||
}
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
retain: true,
|
||||
messageId: 42
|
||||
})
|
||||
publisher.outStream.on('data', function (packet) {
|
||||
const subscriber = connect(setup(broker), { clean: true })
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
t.not(packet.messageId, 42, 'messageId should not be the same')
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
broker.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
broker.on('closed', function () {
|
||||
t.equal(broker.counter, 8)
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.3.1-10]
|
||||
test('clean retained messages', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker), { clean: true })
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 0,
|
||||
retain: true
|
||||
})
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: '',
|
||||
qos: 0,
|
||||
retain: true
|
||||
})
|
||||
const subscriber = connect(setup(broker), { clean: true })
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('should not received retain message')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.3.1-11]
|
||||
test('broker not store zero-byte retained messages', function (t) {
|
||||
t.plan(0)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = connect(setup(broker))
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: '',
|
||||
retain: true
|
||||
})
|
||||
s.broker.on('publish', function (packet, client) {
|
||||
if (packet.topic.startsWith('$SYS/')) {
|
||||
return
|
||||
}
|
||||
const stream = s.broker.persistence.createRetainedStream(packet.topic)
|
||||
stream.pipe(through(function sendRetained (packet, enc, cb) {
|
||||
t.fail('not store zero-byte retained messages')
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
test('fail to clean retained messages without retain flag', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker), { clean: true })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
retain: true,
|
||||
dup: false,
|
||||
length: 12
|
||||
}
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 0,
|
||||
retain: true
|
||||
})
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: '',
|
||||
qos: 0,
|
||||
retain: false
|
||||
})
|
||||
const subscriber = connect(setup(broker), { clean: true })
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('only get the last retained messages in same topic', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker), { clean: true })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('foo'),
|
||||
qos: 0,
|
||||
retain: true,
|
||||
dup: false,
|
||||
length: 10
|
||||
}
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 0,
|
||||
retain: true
|
||||
})
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'foo',
|
||||
qos: 0,
|
||||
retain: true
|
||||
})
|
||||
const subscriber = connect(setup(broker), { clean: true })
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('deliver QoS 1 retained messages to new subscriptions', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: true
|
||||
}
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42,
|
||||
retain: true
|
||||
})
|
||||
|
||||
publisher.outStream.on('data', function (packet) {
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('deliver QoS 1 retained messages to established subscriptions', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42,
|
||||
retain: true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('deliver QoS 0 retained message with QoS 1 subscription', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker))
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 12,
|
||||
retain: true
|
||||
}
|
||||
|
||||
broker.mq.on('hello', function (msg, cb) {
|
||||
cb()
|
||||
|
||||
// defer this or it will receive the message which
|
||||
// is being published
|
||||
setImmediate(function () {
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
messageId: 42,
|
||||
retain: true
|
||||
})
|
||||
})
|
||||
|
||||
test('disconnect and retain messages with QoS 1 [clean=false]', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = noError(connect(setup(broker), { clean: false, clientId: 'abcde' }), t)
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: true
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'disconnect'
|
||||
})
|
||||
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
console.log('original', packet)
|
||||
})
|
||||
|
||||
const publisher = noError(connect(setup(broker)), t)
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 42,
|
||||
retain: true
|
||||
})
|
||||
|
||||
publisher.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
|
||||
t.equal(connect.sessionPresent, true, 'session present is set to true')
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
// receive any queued messages (no matter they are retained messages) at the disconnected time
|
||||
t.not(packet.messageId, 42, 'messageId must differ')
|
||||
delete packet.messageId
|
||||
packet.length = 14
|
||||
t.same(packet, expected, 'packet must match')
|
||||
|
||||
// there should be no messages come from restored subscriptions
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.fail('should not receive any more messages')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('disconnect and two retain messages with QoS 1 [clean=false]', function (t) {
|
||||
t.plan(15)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
let subscriber = noError(connect(setup(broker), { clean: false, clientId: 'abcde' }), t)
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 14,
|
||||
retain: true
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.inStream.write({
|
||||
cmd: 'disconnect'
|
||||
})
|
||||
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
console.log('original', packet)
|
||||
})
|
||||
|
||||
const publisher = noError(connect(setup(broker)), t)
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world',
|
||||
qos: 1,
|
||||
messageId: 41,
|
||||
retain: true
|
||||
})
|
||||
|
||||
publisher.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: 'world2',
|
||||
qos: 1,
|
||||
messageId: 42,
|
||||
retain: true
|
||||
})
|
||||
|
||||
publisher.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'puback')
|
||||
|
||||
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
|
||||
t.equal(connect.sessionPresent, true, 'session present is set to true')
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
// receive any queued messages (included retained messages) at the disconnected time
|
||||
t.not(packet.messageId, 41, 'messageId must differ')
|
||||
delete packet.messageId
|
||||
packet.length = 14
|
||||
expected.payload = Buffer.from('world')
|
||||
t.same(packet, expected, 'packet must match')
|
||||
|
||||
// receive any queued messages (included retained messages) at the disconnected time
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.not(packet.messageId, 42, 'messageId must differ')
|
||||
delete packet.messageId
|
||||
packet.length = 14
|
||||
expected.payload = Buffer.from('world2')
|
||||
t.same(packet, expected, 'packet must match')
|
||||
|
||||
// should get the last retained message when we do a subscribe
|
||||
subscribe(t, subscriber, 'hello', 1, function () {
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
t.not(packet.messageId, 42, 'messageId must differ')
|
||||
delete packet.messageId
|
||||
packet.length = 14
|
||||
expected.payload = Buffer.from('world2')
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { setup, connect, subscribe } = require('./helper')
|
||||
const aedes = require('../')
|
||||
const { validateTopic } = require('../lib/utils')
|
||||
|
||||
test('validation of `null` topic', function (t) {
|
||||
// issue #780
|
||||
t.plan(1)
|
||||
const err = validateTopic(null, 'SUBSCRIBE')
|
||||
t.equal(err.message, 'impossible to SUBSCRIBE to an empty topic')
|
||||
})
|
||||
|
||||
// [MQTT-4.7.1-3]
|
||||
test('Single-level wildcard should match empty level', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
subscribe(t, s, 'a/+/b', 0, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.pass('ok')
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'a//b',
|
||||
payload: 'world'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-4.7.3-1]
|
||||
test('publish empty topic', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const s = connect(setup())
|
||||
|
||||
subscribe(t, s, '#', 0, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.fail('no packet')
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: '',
|
||||
payload: 'world'
|
||||
})
|
||||
|
||||
s.broker.close(function () {
|
||||
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('publish invalid topic with #', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
subscribe(t, s, '#', 0, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.fail('no packet')
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello/#',
|
||||
payload: 'world'
|
||||
})
|
||||
})
|
||||
|
||||
s.broker.on('clientError', function () {
|
||||
t.pass('raise an error')
|
||||
})
|
||||
})
|
||||
|
||||
test('publish invalid topic with +', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
subscribe(t, s, '#', 0, function () {
|
||||
s.outStream.once('data', function (packet) {
|
||||
t.fail('no packet')
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello/+/eee',
|
||||
payload: 'world'
|
||||
})
|
||||
})
|
||||
|
||||
s.broker.on('clientError', function () {
|
||||
t.pass('raise an error')
|
||||
})
|
||||
})
|
||||
|
||||
;['base/#/sub', 'base/#sub', 'base/sub#', 'base/xyz+/sub', 'base/+xyz/sub', ''].forEach(function (topic) {
|
||||
test('subscribe to invalid topic with "' + topic + '"', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('clientError', function () {
|
||||
t.pass('raise an error')
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'subscribe',
|
||||
messageId: 24,
|
||||
subscriptions: [{
|
||||
topic,
|
||||
qos: 0
|
||||
}]
|
||||
})
|
||||
})
|
||||
|
||||
test('unsubscribe to invalid topic with "' + topic + '"', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const s = connect(setup())
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('clientError', function () {
|
||||
t.pass('raise an error')
|
||||
})
|
||||
|
||||
s.inStream.write({
|
||||
cmd: 'unsubscribe',
|
||||
messageId: 24,
|
||||
unsubscriptions: [topic]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('topics are case-sensitive', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const publisher = connect(setup(broker), { clean: true })
|
||||
const subscriber = connect(setup(broker), { clean: true })
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 12,
|
||||
retain: false
|
||||
}
|
||||
|
||||
subscribe(t, subscriber, 'hello', 0, function () {
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
t.same(packet, expected, 'packet mush match')
|
||||
})
|
||||
;['hello', 'HELLO', 'heLLo', 'HELLO/#', 'hello/+'].forEach(function (topic) {
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic,
|
||||
payload: 'world',
|
||||
qos: 0,
|
||||
retain: false
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function subscribeMultipleTopics (t, broker, qos, subscriber, subscriptions, done) {
|
||||
const publisher = connect(setup(broker))
|
||||
subscriber.inStream.write({
|
||||
cmd: 'subscribe',
|
||||
messageId: 24,
|
||||
subscriptions
|
||||
})
|
||||
|
||||
subscriber.outStream.once('data', function (packet) {
|
||||
t.equal(packet.cmd, 'suback')
|
||||
t.same(packet.granted, subscriptions.map(obj => obj.qos))
|
||||
t.equal(packet.messageId, 24)
|
||||
|
||||
publisher.inStream.write({
|
||||
cmd: 'publish',
|
||||
topic: 'hello/world',
|
||||
payload: 'world',
|
||||
qos,
|
||||
messageId: 42
|
||||
})
|
||||
|
||||
if (done) {
|
||||
done(null, packet)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
test('Overlapped topics with same QoS', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello/world',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 1,
|
||||
dup: false,
|
||||
length: 20,
|
||||
retain: false
|
||||
}
|
||||
const sub = [
|
||||
{ topic: 'hello/world', qos: 1 },
|
||||
{ topic: 'hello/#', qos: 1 }]
|
||||
subscribeMultipleTopics(t, broker, 1, subscriber, sub, function () {
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.3.5-1]
|
||||
test('deliver overlapped topics respecting the maximum QoS of all the matching subscriptions - QoS 0 publish', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello/world',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 18,
|
||||
retain: false
|
||||
}
|
||||
const sub = [
|
||||
{ topic: 'hello/world', qos: 0 },
|
||||
{ topic: 'hello/#', qos: 2 }]
|
||||
subscribeMultipleTopics(t, broker, 0, subscriber, sub, function () {
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
delete packet.messageId
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// [MQTT-3.3.5-1]
|
||||
test('deliver overlapped topics respecting the maximum QoS of all the matching subscriptions - QoS 2 publish', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const subscriber = connect(setup(broker))
|
||||
|
||||
const sub = [
|
||||
{ topic: 'hello/world', qos: 0 },
|
||||
{ topic: 'hello/#', qos: 2 }]
|
||||
subscribeMultipleTopics(t, broker, 2, subscriber, sub, function () {
|
||||
subscriber.outStream.on('data', function () {
|
||||
t.fail('should receive messages with the maximum QoS')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Overlapped topics with QoS downgrade', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const subscriber = connect(setup(broker))
|
||||
const expected = {
|
||||
cmd: 'publish',
|
||||
topic: 'hello/world',
|
||||
payload: Buffer.from('world'),
|
||||
qos: 0,
|
||||
dup: false,
|
||||
length: 18,
|
||||
retain: false
|
||||
}
|
||||
const sub = [
|
||||
{ topic: 'hello/world', qos: 1 },
|
||||
{ topic: 'hello/#', qos: 1 }]
|
||||
subscribeMultipleTopics(t, broker, 0, subscriber, sub, function () {
|
||||
subscriber.outStream.on('data', function (packet) {
|
||||
t.same(packet, expected, 'packet must match')
|
||||
})
|
||||
})
|
||||
})
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
import { IncomingMessage } from 'node:http'
|
||||
import { Socket } from 'node:net'
|
||||
import type {
|
||||
Brokers,
|
||||
AuthenticateError,
|
||||
Client,
|
||||
Connection
|
||||
} from '../../aedes'
|
||||
import Aedes, { AedesOptions, createBroker } from '../../aedes'
|
||||
import type { AedesPublishPacket, ConnackPacket, ConnectPacket, PingreqPacket, PublishPacket, PubrelPacket, Subscription, SubscribePacket, UnsubscribePacket } from '../../types/packet'
|
||||
import { expectType } from 'tsd'
|
||||
|
||||
// Test for createBroker function
|
||||
expectType<(options?: AedesOptions) => Aedes>(createBroker)
|
||||
|
||||
// Aedes server
|
||||
let broker = createBroker()
|
||||
expectType<Aedes>(broker)
|
||||
|
||||
broker = new Aedes({
|
||||
id: 'aedes',
|
||||
concurrency: 100,
|
||||
heartbeatInterval: 60000,
|
||||
connectTimeout: 30000,
|
||||
maxClientsIdLength: 23,
|
||||
keepaliveLimit: 0,
|
||||
preConnect: (client: Client, packet: ConnectPacket, callback) => {
|
||||
if (client.req) {
|
||||
callback(new Error('not websocket stream'), false)
|
||||
}
|
||||
if (client.conn instanceof Socket && client.conn.remoteAddress === '::1') {
|
||||
callback(null, true)
|
||||
} else {
|
||||
callback(new Error('connection error'), false)
|
||||
}
|
||||
},
|
||||
authenticate: (
|
||||
client: Client,
|
||||
username: Readonly<string | undefined>,
|
||||
password: Readonly<Buffer | undefined>,
|
||||
callback
|
||||
) => {
|
||||
if (
|
||||
username === 'test' &&
|
||||
password === Buffer.from('test') &&
|
||||
client.version === 4
|
||||
) {
|
||||
callback(null, true)
|
||||
} else {
|
||||
const error = new Error() as AuthenticateError
|
||||
error.returnCode = 1
|
||||
|
||||
callback(error, false)
|
||||
}
|
||||
},
|
||||
authorizePublish: (
|
||||
client: Client | null,
|
||||
packet: PublishPacket,
|
||||
callback
|
||||
) => {
|
||||
if (packet.topic === 'aaaa') {
|
||||
return callback(new Error('wrong topic'))
|
||||
}
|
||||
|
||||
if (packet.topic === 'bbb') {
|
||||
packet.payload = Buffer.from('overwrite packet payload')
|
||||
}
|
||||
|
||||
callback(null)
|
||||
},
|
||||
authorizeSubscribe: (client: Client, sub: Subscription, callback) => {
|
||||
if (sub.topic === 'aaaa') {
|
||||
return callback(new Error('wrong topic'))
|
||||
}
|
||||
|
||||
if (sub.topic === 'bbb') {
|
||||
// overwrites subscription
|
||||
sub.qos = 2
|
||||
}
|
||||
|
||||
callback(null, sub)
|
||||
},
|
||||
authorizeForward: (client: Client, packet: AedesPublishPacket) => {
|
||||
if (packet.topic === 'aaaa' && client.id === 'I should not see this') {
|
||||
return null
|
||||
// also works with return undefined
|
||||
} else if (
|
||||
packet.topic === 'aaaa' &&
|
||||
client.id === 'I should not see this either'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (packet.topic === 'bbb') {
|
||||
packet.payload = Buffer.from('overwrite packet payload')
|
||||
}
|
||||
|
||||
return packet
|
||||
},
|
||||
published: (packet: AedesPublishPacket, client: Client, callback) => {
|
||||
callback(null)
|
||||
callback(new Error())
|
||||
}
|
||||
})
|
||||
|
||||
expectType<Aedes>(broker)
|
||||
|
||||
expectType<Readonly<Brokers>>(broker.brokers)
|
||||
|
||||
expectType<Aedes>(broker.on('closed', () => {}))
|
||||
expectType<Aedes>(broker.on('client', (client: Client) => {}))
|
||||
expectType<Aedes>(broker.on('clientReady', (client: Client) => {}))
|
||||
expectType<Aedes>(broker.on('clientDisconnect', (client: Client) => {}))
|
||||
expectType<Aedes>(broker.on('keepaliveTimeout', (client: Client) => {}))
|
||||
expectType<Aedes>(
|
||||
broker.on('clientError', (client: Client, error: Error) => {})
|
||||
)
|
||||
expectType<Aedes>(
|
||||
broker.on('connectionError', (client: Client, error: Error) => {})
|
||||
)
|
||||
expectType<Aedes>(
|
||||
broker.on('connackSent', (packet: ConnackPacket, client: Client) => {})
|
||||
)
|
||||
expectType<Aedes>(
|
||||
broker.on('ping', (packet: PingreqPacket, client: Client) => {})
|
||||
)
|
||||
expectType<Aedes>(
|
||||
broker.on(
|
||||
'publish',
|
||||
(packet: AedesPublishPacket, client: Client | null) => {}
|
||||
)
|
||||
)
|
||||
expectType<Aedes>(
|
||||
broker.on('ack', (packet: PublishPacket | PubrelPacket, client: Client) => {})
|
||||
)
|
||||
expectType<Aedes>(
|
||||
broker.on('subscribe', (subscriptions: Subscription[], client: Client) => {})
|
||||
)
|
||||
expectType<Aedes>(
|
||||
broker.on('unsubscribe', (unsubscriptions: string[], client: Client) => {})
|
||||
)
|
||||
|
||||
expectType<void>(
|
||||
broker.publish({} as PublishPacket, (error?: Error) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
expectType<void>(
|
||||
broker.subscribe(
|
||||
'topic',
|
||||
(packet: AedesPublishPacket, callback: () => void) => {},
|
||||
() => {}
|
||||
)
|
||||
)
|
||||
|
||||
expectType<void>(
|
||||
broker.unsubscribe(
|
||||
'topic',
|
||||
(packet: AedesPublishPacket, callback: () => void) => {},
|
||||
() => {}
|
||||
)
|
||||
)
|
||||
|
||||
expectType<void>(broker.close())
|
||||
expectType<void>(broker.close(() => {}))
|
||||
|
||||
// Aedes client
|
||||
const client = broker.handle({} as Connection, {} as IncomingMessage)
|
||||
const client2 = broker.handle({} as Connection)
|
||||
|
||||
expectType<Client>(client)
|
||||
expectType<Client>(client2)
|
||||
|
||||
expectType<Connection>(client.conn)
|
||||
expectType<IncomingMessage>(client.req!)
|
||||
|
||||
expectType<Client>(client.on('connected', () => {}))
|
||||
expectType<Client>(
|
||||
client.on('error', (error: Error) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
expectType<void>(
|
||||
client.publish({} as PublishPacket, (error?: Error) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
)
|
||||
expectType<void>(client.publish({} as PublishPacket))
|
||||
|
||||
expectType<void>(
|
||||
client.subscribe({} as Subscription, (error?: Error) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
)
|
||||
expectType<void>(client.subscribe({} as Subscription))
|
||||
expectType<void>(client.subscribe([] as Subscription[]))
|
||||
expectType<void>(client.subscribe({} as SubscribePacket))
|
||||
|
||||
expectType<void>(
|
||||
client.unsubscribe({} as Subscription, (error?: Error) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
)
|
||||
expectType<void>(client.unsubscribe({} as Subscription))
|
||||
expectType<void>(client.unsubscribe([] as Subscription[]))
|
||||
expectType<void>(client.unsubscribe({} as UnsubscribePacket))
|
||||
|
||||
expectType<void>(client.emptyOutgoingQueue())
|
||||
expectType<void>(client.emptyOutgoingQueue(() => {}))
|
||||
|
||||
expectType<void>(client.close())
|
||||
expectType<void>(client.close(() => {}))
|
||||
+601
@@ -0,0 +1,601 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const memory = require('aedes-persistence')
|
||||
const Faketimers = require('@sinonjs/fake-timers')
|
||||
const { setup, connect, noError } = require('./helper')
|
||||
const aedes = require('../')
|
||||
|
||||
function willConnect (s, opts, connected) {
|
||||
opts = opts || {}
|
||||
opts.will = {
|
||||
topic: 'mywill',
|
||||
payload: Buffer.from('last will'),
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
|
||||
return connect(s, opts, connected)
|
||||
}
|
||||
|
||||
test('delivers a will', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const opts = {}
|
||||
// willConnect populates opts with a will
|
||||
const s = willConnect(setup(),
|
||||
opts,
|
||||
function () {
|
||||
s.conn.destroy()
|
||||
}
|
||||
)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.mq.on('mywill', function (packet, cb) {
|
||||
t.equal(packet.topic, opts.will.topic, 'topic matches')
|
||||
t.same(packet.payload, opts.will.payload, 'payload matches')
|
||||
t.equal(packet.qos, opts.will.qos, 'qos matches')
|
||||
t.equal(packet.retain, opts.will.retain, 'retain matches')
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
test('calling close two times should not deliver two wills', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const opts = {}
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.on('client', function (client) {
|
||||
client.close()
|
||||
client.close()
|
||||
})
|
||||
|
||||
broker.mq.on('mywill', onWill)
|
||||
|
||||
// willConnect populates opts with a will
|
||||
willConnect(setup(broker), opts)
|
||||
|
||||
function onWill (packet, cb) {
|
||||
broker.mq.removeListener('mywill', onWill, function () {
|
||||
broker.mq.on('mywill', function (packet) {
|
||||
t.fail('the will must be delivered only once')
|
||||
})
|
||||
})
|
||||
t.equal(packet.topic, opts.will.topic, 'topic matches')
|
||||
t.same(packet.payload, opts.will.payload, 'payload matches')
|
||||
t.equal(packet.qos, opts.will.qos, 'qos matches')
|
||||
t.equal(packet.retain, opts.will.retain, 'retain matches')
|
||||
cb()
|
||||
}
|
||||
})
|
||||
|
||||
test('delivers old will in case of a crash', function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const persistence = memory()
|
||||
const will = {
|
||||
topic: 'mywill',
|
||||
payload: Buffer.from('last will'),
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
|
||||
persistence.broker = {
|
||||
id: 'anotherBroker'
|
||||
}
|
||||
|
||||
persistence.putWill({
|
||||
id: 'myClientId42'
|
||||
}, will, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
let authorized = false
|
||||
const interval = 10 // ms, so that the will check happens fast!
|
||||
const broker = aedes({
|
||||
persistence,
|
||||
heartbeatInterval: interval,
|
||||
authorizePublish: function (client, packet, callback) {
|
||||
t.strictSame(client, null, 'client must be null')
|
||||
authorized = true
|
||||
callback(null)
|
||||
}
|
||||
})
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const start = Date.now()
|
||||
|
||||
broker.mq.on('mywill', check)
|
||||
|
||||
function check (packet, cb) {
|
||||
broker.mq.removeListener('mywill', check, function () {
|
||||
broker.mq.on('mywill', function (packet) {
|
||||
t.fail('the will must be delivered only once')
|
||||
})
|
||||
})
|
||||
t.ok(Date.now() - start >= 3 * interval, 'the will needs to be emitted after 3 heartbeats')
|
||||
t.equal(packet.topic, will.topic, 'topic matches')
|
||||
t.same(packet.payload, will.payload, 'payload matches')
|
||||
t.equal(packet.qos, will.qos, 'qos matches')
|
||||
t.equal(packet.retain, will.retain, 'retain matches')
|
||||
t.equal(authorized, true, 'authorization called')
|
||||
cb()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('deliver old will without authorization in case of a crash', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const persistence = memory()
|
||||
const will = {
|
||||
topic: 'mywill',
|
||||
payload: Buffer.from('last will'),
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
|
||||
persistence.broker = {
|
||||
id: 'anotherBroker'
|
||||
}
|
||||
|
||||
persistence.putWill({
|
||||
id: 'myClientId42'
|
||||
}, will, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
const interval = 10 // ms, so that the will check happens fast!
|
||||
const broker = aedes({
|
||||
persistence,
|
||||
heartbeatInterval: interval,
|
||||
authorizePublish: function (client, packet, callback) {
|
||||
t.strictSame(client, null, 'client must be null')
|
||||
callback(new Error())
|
||||
}
|
||||
})
|
||||
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.mq.on('mywill', check)
|
||||
|
||||
function check (packet, cb) {
|
||||
t.fail('received will without authorization')
|
||||
cb()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('delete old broker', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const clock = Faketimers.install()
|
||||
|
||||
const heartbeatInterval = 100
|
||||
const broker = aedes({
|
||||
heartbeatInterval
|
||||
})
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const brokerId = 'dummyBroker'
|
||||
|
||||
broker.brokers[brokerId] = Date.now() - heartbeatInterval * 3.5
|
||||
|
||||
setTimeout(() => {
|
||||
t.equal(broker.brokers[brokerId], undefined, 'Broker deleted')
|
||||
}, heartbeatInterval * 4)
|
||||
|
||||
clock.tick(heartbeatInterval * 4)
|
||||
|
||||
clock.uninstall()
|
||||
})
|
||||
|
||||
test('store the will in the persistence', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const opts = {
|
||||
clientId: 'abcde'
|
||||
}
|
||||
|
||||
// willConnect populates opts with a will
|
||||
const s = willConnect(setup(), opts)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('client', function () {
|
||||
// this is connack
|
||||
s.broker.persistence.getWill({
|
||||
id: opts.clientId
|
||||
}, function (err, packet) {
|
||||
t.error(err, 'no error')
|
||||
t.same(packet.topic, opts.will.topic, 'will topic matches')
|
||||
t.same(packet.payload, opts.will.payload, 'will payload matches')
|
||||
t.same(packet.qos, opts.will.qos, 'will qos matches')
|
||||
t.same(packet.retain, opts.will.retain, 'will retain matches')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('delete the will in the persistence after publish', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const opts = {
|
||||
clientId: 'abcde'
|
||||
}
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.on('client', function (client) {
|
||||
setImmediate(function () {
|
||||
client.close()
|
||||
})
|
||||
})
|
||||
|
||||
broker.mq.on('mywill', check)
|
||||
|
||||
// willConnect populates opts with a will
|
||||
willConnect(setup(broker), opts)
|
||||
|
||||
function check (packet, cb) {
|
||||
broker.mq.removeListener('mywill', check, function () {
|
||||
broker.persistence.getWill({
|
||||
id: opts.clientId
|
||||
}, function (err, p) {
|
||||
t.error(err, 'no error')
|
||||
t.notOk(p, 'packet is empty')
|
||||
})
|
||||
})
|
||||
cb()
|
||||
}
|
||||
})
|
||||
|
||||
test('delivers a will with authorization', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
let authorized = false
|
||||
const opts = {}
|
||||
// willConnect populates opts with a will
|
||||
const s = willConnect(
|
||||
setup(aedes({
|
||||
authorizePublish: (client, packet, callback) => {
|
||||
authorized = true
|
||||
callback(null)
|
||||
}
|
||||
})),
|
||||
opts,
|
||||
function () {
|
||||
s.conn.destroy()
|
||||
}
|
||||
)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('clientDisconnect', function (client) {
|
||||
t.equal(client.connected, false)
|
||||
})
|
||||
|
||||
s.broker.mq.on('mywill', function (packet, cb) {
|
||||
t.equal(packet.topic, opts.will.topic, 'topic matches')
|
||||
t.same(packet.payload, opts.will.payload, 'payload matches')
|
||||
t.equal(packet.qos, opts.will.qos, 'qos matches')
|
||||
t.equal(packet.retain, opts.will.retain, 'retain matches')
|
||||
t.equal(authorized, true, 'authorization called')
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
test('delivers a will waits for authorization', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
let authorized = false
|
||||
const opts = {}
|
||||
// willConnect populates opts with a will
|
||||
const s = willConnect(
|
||||
setup(aedes({
|
||||
authorizePublish: (client, packet, callback) => {
|
||||
authorized = true
|
||||
setImmediate(() => { callback(null) })
|
||||
}
|
||||
})),
|
||||
opts,
|
||||
function () {
|
||||
s.conn.destroy()
|
||||
}
|
||||
)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('clientDisconnect', function () {
|
||||
t.pass('client is disconnected')
|
||||
})
|
||||
|
||||
s.broker.mq.on('mywill', function (packet, cb) {
|
||||
t.equal(packet.topic, opts.will.topic, 'topic matches')
|
||||
t.same(packet.payload, opts.will.payload, 'payload matches')
|
||||
t.equal(packet.qos, opts.will.qos, 'qos matches')
|
||||
t.equal(packet.retain, opts.will.retain, 'retain matches')
|
||||
t.equal(authorized, true, 'authorization called')
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
test('does not deliver a will without authorization', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
let authorized = false
|
||||
const opts = {}
|
||||
// willConnect populates opts with a will
|
||||
const s = willConnect(
|
||||
setup(aedes({
|
||||
authorizePublish: (username, packet, callback) => {
|
||||
authorized = true
|
||||
callback(new Error())
|
||||
}
|
||||
})),
|
||||
opts,
|
||||
function () {
|
||||
s.conn.destroy()
|
||||
}
|
||||
)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('clientDisconnect', function () {
|
||||
t.equal(authorized, true, 'authorization called')
|
||||
})
|
||||
|
||||
s.broker.mq.on('mywill', function (packet, cb) {
|
||||
t.fail('received will without authorization')
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
test('does not deliver a will without authentication', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
let authenticated = false
|
||||
const opts = {}
|
||||
// willConnect populates opts with a will
|
||||
const s = willConnect(
|
||||
setup(aedes({
|
||||
authenticate: (client, username, password, callback) => {
|
||||
authenticated = true
|
||||
callback(new Error(), false)
|
||||
}
|
||||
})),
|
||||
opts
|
||||
)
|
||||
t.teardown(s.broker.close.bind(s.broker))
|
||||
|
||||
s.broker.on('clientError', function () {
|
||||
t.equal(authenticated, true, 'authentication called')
|
||||
t.end()
|
||||
})
|
||||
|
||||
s.broker.mq.on('mywill', function (packet, cb) {
|
||||
t.fail('received will without authentication')
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
test('does not deliver will if broker is closed during authentication', function (t) {
|
||||
t.plan(0)
|
||||
|
||||
const opts = { keepalive: 1 }
|
||||
|
||||
const broker = aedes({
|
||||
authenticate: function (client, username, password, callback) {
|
||||
setTimeout(function () {
|
||||
callback(null, true)
|
||||
})
|
||||
broker.close()
|
||||
}
|
||||
})
|
||||
|
||||
broker.on('keepaliveTimeout', function () {
|
||||
t.fail('keepalive timer shoud not be set')
|
||||
})
|
||||
|
||||
broker.mq.on('mywill', function (packet, cb) {
|
||||
t.fail('Received will when it was not expected')
|
||||
cb()
|
||||
})
|
||||
|
||||
willConnect(setup(broker), opts)
|
||||
})
|
||||
|
||||
// [MQTT-3.14.4-3]
|
||||
test('does not deliver will when client sends a DISCONNECT', function (t) {
|
||||
t.plan(0)
|
||||
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = noError(willConnect(setup(broker), {}, function () {
|
||||
s.inStream.end({
|
||||
cmd: 'disconnect'
|
||||
})
|
||||
}), t)
|
||||
|
||||
s.broker.mq.on('mywill', function (packet, cb) {
|
||||
t.fail(packet)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
test('deletes from persistence on DISCONNECT', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const opts = {
|
||||
clientId: 'abcde'
|
||||
}
|
||||
const broker = aedes()
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const s = noError(willConnect(setup(broker), opts, function () {
|
||||
s.inStream.end({
|
||||
cmd: 'disconnect'
|
||||
})
|
||||
}), t)
|
||||
|
||||
s.broker.persistence.getWill({
|
||||
id: opts.clientId
|
||||
}, function (err, packet) {
|
||||
t.error(err, 'no error')
|
||||
t.notOk(packet)
|
||||
})
|
||||
})
|
||||
|
||||
test('does not store multiple will with same clientid', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const opts = { clientId: 'abcde' }
|
||||
|
||||
const broker = aedes()
|
||||
|
||||
let s = noError(willConnect(setup(broker), opts, function () {
|
||||
// gracefully close client so no will is sent
|
||||
s.inStream.end({
|
||||
cmd: 'disconnect'
|
||||
})
|
||||
}), t)
|
||||
|
||||
broker.on('clientDisconnect', function (client) {
|
||||
// reconnect same client with will
|
||||
s = willConnect(setup(broker), opts, function () {
|
||||
// check that there are not 2 will messages for the same clientid
|
||||
s.broker.persistence.delWill({ id: opts.clientId }, function (err, packet) {
|
||||
t.error(err, 'no error')
|
||||
t.equal(packet.clientId, opts.clientId, 'will packet found')
|
||||
s.broker.persistence.delWill({ id: opts.clientId }, function (err, packet) {
|
||||
t.error(err, 'no error')
|
||||
t.equal(!!packet, false, 'no duplicated packets')
|
||||
broker.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('don\'t delivers a will if broker alive', function (t) {
|
||||
const persistence = memory()
|
||||
const will = {
|
||||
topic: 'mywill',
|
||||
payload: Buffer.from('last will'),
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
|
||||
const oldBroker = 'broker1'
|
||||
|
||||
persistence.broker = {
|
||||
id: oldBroker
|
||||
}
|
||||
|
||||
persistence.putWill({
|
||||
id: 'myClientId42'
|
||||
}, will, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
const opts = {
|
||||
persistence,
|
||||
heartbeatInterval: 10
|
||||
}
|
||||
|
||||
let count = 0
|
||||
|
||||
const broker = aedes(opts)
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
const streamWill = persistence.streamWill
|
||||
persistence.streamWill = function () {
|
||||
// don't pass broker.brokers to streamWill
|
||||
return streamWill.call(persistence)
|
||||
}
|
||||
|
||||
broker.mq.on('mywill', function (packet, cb) {
|
||||
t.fail('Will received')
|
||||
cb()
|
||||
})
|
||||
|
||||
broker.mq.on('$SYS/+/heartbeat', function () {
|
||||
t.pass('Heartbeat received')
|
||||
broker.brokers[oldBroker] = Date.now()
|
||||
|
||||
if (++count === 5) {
|
||||
t.end()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('handle will publish error', function (t) {
|
||||
t.plan(2)
|
||||
const persistence = memory()
|
||||
const will = {
|
||||
topic: 'mywill',
|
||||
payload: Buffer.from('last will'),
|
||||
qos: 0,
|
||||
retain: false
|
||||
}
|
||||
|
||||
persistence.broker = {
|
||||
id: 'broker1'
|
||||
}
|
||||
|
||||
persistence.putWill({
|
||||
id: 'myClientId42'
|
||||
}, will, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
const opts = {
|
||||
persistence,
|
||||
heartbeatInterval: 10
|
||||
}
|
||||
|
||||
persistence.delWill = function (client, cb) {
|
||||
cb(new Error('Throws error'))
|
||||
}
|
||||
|
||||
const broker = aedes(opts)
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.once('error', function (err) {
|
||||
t.equal('Throws error', err.message, 'throws error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('handle will publish error 2', function (t) {
|
||||
t.plan(2)
|
||||
const persistence = memory()
|
||||
const will = {
|
||||
topic: 'mywill',
|
||||
payload: Buffer.from('last will'),
|
||||
qos: 0,
|
||||
retain: true
|
||||
}
|
||||
|
||||
persistence.broker = {
|
||||
id: 'broker1'
|
||||
}
|
||||
|
||||
persistence.putWill({
|
||||
id: 'myClientId42'
|
||||
}, will, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
const opts = {
|
||||
persistence,
|
||||
heartbeatInterval: 10
|
||||
}
|
||||
|
||||
persistence.storeRetained = function (packet, cb) {
|
||||
cb(new Error('Throws error'))
|
||||
}
|
||||
|
||||
const broker = aedes(opts)
|
||||
t.teardown(broker.close.bind(broker))
|
||||
|
||||
broker.once('error', function (err) {
|
||||
t.equal('Throws error', err.message, 'throws error')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user