首次提交

This commit is contained in:
2026-07-13 12:06:16 +08:00
commit e3c810b1a6
1690 changed files with 228324 additions and 0 deletions
+686
View File
@@ -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')
})
})