Files
HarmonyOS--waterapp/mqtt-backend/node_modules/aedes/test/client-pub-sub.js
T
2026-07-13 12:06:16 +08:00

1028 lines
22 KiB
JavaScript

'use strict'
const { test } = require('tap')
const { setup, connect, subscribe, noError } = require('./helper')
const aedes = require('../')
test('publish direct to a single client QoS 0', function (t) {
t.plan(2)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
dup: false,
length: 12,
qos: 0,
retain: false
}
broker.on('client', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 0
}, function (err) {
t.error(err, 'no error')
})
})
const s = connect(setup(broker))
s.outStream.once('data', function (packet) {
t.same(packet, expected, 'packet matches')
})
})
test('publish direct to a single client throws error', function (t) {
t.plan(1)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.persistence.outgoingEnqueue = function (sub, packet, done) {
done(new Error('Throws error'))
}
broker.on('client', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 1,
retain: false
}, function (err) {
t.pass('Throws error', err.message, 'throws error')
})
})
connect(setup(broker), { clean: false })
})
test('publish direct to a single client throws error 2', function (t) {
t.plan(1)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.persistence.outgoingUpdate = function (client, packet, done) {
done(new Error('Throws error'), client, packet)
}
broker.on('client', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 1,
retain: false
}, () => {})
client.once('error', function (err) {
t.pass('Throws error', err.message, 'throws error')
})
})
connect(setup(broker), { clean: false })
})
test('publish direct to a single client QoS 1', function (t) {
t.plan(2)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
dup: false,
length: 14,
qos: 1,
retain: false
}
broker.on('client', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 1
}, function (err) {
t.error(err, 'no error')
})
})
const s = connect(setup(broker))
s.outStream.once('data', function (packet) {
expected.messageId = packet.messageId
t.same(packet, expected, 'packet matches')
s.inStream.write({
cmd: 'puback',
messageId: packet.messageId
})
})
})
test('publish QoS 2 throws error in pubrel', function (t) {
t.plan(2)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const s = connect(setup(broker))
broker.on('clientError', function (c, err) {
t.pass('throws error')
})
s.outStream.on('data', function (packet) {
if (packet.cmd === 'publish') {
s.inStream.write({
cmd: 'pubrec',
messageId: packet.messageId
})
s.broker.persistence.outgoingUpdate = function (client, pubrel, cb) {
cb(new Error('error'))
}
}
})
broker.on('clientReady', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 2
}, function (err) {
t.error(err, 'no error')
})
})
})
test('publish direct to a single client QoS 2', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
let publishCount = 0
let nonPublishCount = 0
broker.on('clientReady', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 2
}, function (err) {
t.error(err, 'no error')
})
client.on('error', function (err) {
t.error(err)
})
})
const s = connect(setup(broker))
s.inStream.on('close', () => {
t.equal(publishCount, 1)
t.equal(nonPublishCount, 1)
})
s.outStream.on('data', function (packet) {
if (packet.cmd === 'publish') {
publishCount++
s.inStream.write({
cmd: 'pubrec',
messageId: packet.messageId
})
} else {
nonPublishCount++
s.inStream.write({
cmd: 'pubcomp',
messageId: packet.messageId
})
s.inStream.destroy()
}
})
})
test('emit a `ack` event on PUBACK for QoS 1 [clean=false]', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
qos: 1,
retain: false,
dup: false
}
broker.on('clientReady', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 1
}, function (err) {
t.error(err, 'no error')
})
})
broker.once('ack', function (packet, client) {
expected.brokerId = packet.brokerId
expected.brokerCounter = packet.brokerCounter
expected.messageId = packet.messageId
t.same(packet, expected, 'ack packet is origianl packet')
t.pass('got the ack event')
})
const s = connect(setup(broker), { clean: false })
s.outStream.once('data', function (packet) {
s.inStream.write({
cmd: 'puback',
messageId: packet.messageId
})
})
})
test('emit a `ack` event on PUBACK for QoS 1 [clean=true]', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.on('clientReady', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 1
}, function (err) {
t.error(err, 'no error')
})
})
broker.once('ack', function (packet, client) {
t.equal(packet, undefined, 'ack packet is undefined')
t.pass('got the ack event')
})
const s = connect(setup(broker), { clean: true })
s.outStream.once('data', function (packet) {
s.inStream.write({
cmd: 'puback',
messageId: packet.messageId
})
})
})
test('emit a `ack` event on PUBCOMP for QoS 2 [clean=false]', function (t) {
t.plan(5)
const broker = aedes()
t.teardown(broker.close.bind(broker))
let messageId
let clientId
broker.on('clientReady', function (client) {
clientId = client.id
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 2
}, function (err) {
t.error(err, 'no error')
})
})
broker.once('ack', function (packet, client) {
t.equal(client.id, clientId)
t.equal(packet.messageId, messageId)
t.equal(packet.cmd, 'pubrel', 'ack packet is purel')
t.pass('got the ack event')
})
const s = connect(setup(broker), { clean: false })
s.outStream.on('data', function (packet) {
if (packet.cmd === 'publish') {
s.inStream.write({
cmd: 'pubrec',
messageId: packet.messageId
})
} else {
messageId = packet.messageId
s.inStream.write({
cmd: 'pubcomp',
messageId: packet.messageId
})
}
})
})
test('emit a `ack` event on PUBCOMP for QoS 2 [clean=true]', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.on('clientReady', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 2
}, function (err) {
t.error(err, 'no error')
})
})
broker.once('ack', function (packet, client) {
t.equal(packet, undefined, 'ack packet is undefined')
t.pass('got the ack event')
})
const s = connect(setup(broker), { clean: true })
s.outStream.on('data', function (packet) {
if (packet.cmd === 'publish') {
s.inStream.write({
cmd: 'pubrec',
messageId: packet.messageId
})
} else {
s.inStream.write({
cmd: 'pubcomp',
messageId: packet.messageId
})
}
})
})
test('offline message support for direct publish', function (t) {
t.plan(2)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
dup: false,
length: 14,
qos: 1,
retain: false
}
const opts = {
clean: false,
clientId: 'abcde'
}
broker.once('client', function (client) {
client.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 1
}, function (err) {
t.error(err, 'no error')
})
})
let s = connect(setup(broker), opts)
s.outStream.once('data', function (packet) {
s = connect(setup(broker), opts)
s.outStream.once('data', function (packet) {
s = connect(setup(broker), opts)
s.inStream.write({
cmd: 'puback',
messageId: packet.messageId
})
delete packet.messageId
t.same(packet, expected, 'packet must match')
})
})
})
test('subscribe a client programmatically', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
dup: false,
length: 12,
qos: 0,
retain: false
}
broker.on('client', function (client) {
client.subscribe({
topic: 'hello',
qos: 0
}, function (err) {
t.error(err, 'no error')
broker.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 0
}, function (err) {
t.error(err, 'no error')
})
})
})
const s = connect(setup(broker))
s.outStream.once('data', function (packet) {
t.same(packet, expected, 'packet matches')
})
})
test('subscribe a client programmatically clears retain', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
dup: false,
length: 12,
qos: 0,
retain: false
}
broker.on('client', function (client) {
client.subscribe({
topic: 'hello',
qos: 0
}, function (err) {
t.error(err, 'no error')
broker.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 0,
retain: true
}, function (err) {
t.error(err, 'no error')
})
})
})
const s = connect(setup(broker))
s.outStream.once('data', function (packet) {
t.same(packet, expected, 'packet matches')
})
})
test('subscribe a bridge programmatically keeps retain', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
dup: false,
length: 12,
qos: 0,
retain: true
}
broker.on('client', function (client) {
client.subscribe({
topic: 'hello',
qos: 0,
rap: true
}, function (err) {
t.error(err, 'no error')
broker.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 0,
retain: true
}, function (err) {
t.error(err, 'no error')
})
})
})
const s = connect(setup(broker))
s.outStream.once('data', function (packet) {
t.same(packet, expected, 'packet matches')
})
})
test('subscribe throws error when QoS > 0', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.on('clientReady', function (client) {
client.subscribe({
topic: 'hello',
qos: 1
}, function (err) {
t.error(err, 'no error')
// makes writeQos throw error
client.connected = false
client.connecting = false
broker.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 1
}, function (err) {
t.error(err, 'no error')
})
})
})
broker.on('clientError', function (client, error) {
t.equal(error.message, 'connection closed', 'should throw clientError')
})
connect(setup(broker))
})
test('subscribe a client programmatically - wildcard', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello/world/1',
payload: Buffer.from('world'),
dup: false,
length: 20,
qos: 0,
retain: false
}
broker.on('clientReady', function (client) {
client.subscribe({
topic: '+/world/1',
qos: 0
}, function (err) {
t.error(err, 'no error')
broker.publish({
topic: 'hello/world/1',
payload: Buffer.from('world'),
qos: 0
}, function (err) {
t.error(err, 'no error')
})
})
})
const s = connect(setup(broker))
s.outStream.once('data', function (packet) {
t.same(packet, expected, 'packet matches')
})
})
test('unsubscribe a client', function (t) {
t.plan(2)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.on('client', function (client) {
client.subscribe({
topic: 'hello',
qos: 0
}, function (err) {
t.error(err, 'no error')
client.unsubscribe([{
topic: 'hello',
qos: 0
}], function (err) {
t.error(err, 'no error')
})
})
})
connect(setup(broker))
})
test('unsubscribe should not call removeSubscriptions when [clean=true]', function (t) {
t.plan(2)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.persistence.removeSubscriptions = function (client, subs, cb) {
cb(Error('remove subscription is called'))
}
broker.on('client', function (client) {
client.subscribe({
topic: 'hello',
qos: 1
}, function (err) {
t.error(err, 'no error')
client.unsubscribe({
unsubscriptions: [{
topic: 'hello',
qos: 1
}],
messageId: 42
}, function (err) {
t.error(err, 'no error')
})
})
})
connect(setup(broker), { clean: true })
})
test('unsubscribe throws error', function (t) {
t.plan(2)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.on('client', function (client) {
client.subscribe({
topic: 'hello',
qos: 0
}, function (err) {
t.error(err, 'no error')
broker.unsubscribe = function (topic, func, done) {
done(new Error('error'))
}
client.unsubscribe({
topic: 'hello',
qos: 0
}, function () {
t.pass('throws error')
})
})
})
connect(setup(broker))
})
test('unsubscribe throws error 2', function (t) {
t.plan(2)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.on('client', function (client) {
client.subscribe({
topic: 'hello',
qos: 2
}, function (err) {
t.error(err, 'no error')
broker.persistence.removeSubscriptions = function (client, unsubscriptions, done) {
done(new Error('error'))
}
client.unsubscribe({
unsubscriptions: [{
topic: 'hello',
qos: 2
}],
messageId: 42
}, function () {
t.pass('throws error')
})
})
})
connect(setup(broker))
})
test('subscribe a client programmatically multiple topics', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
dup: false,
length: 12,
qos: 0,
retain: false
}
broker.on('client', function (client) {
client.subscribe([{
topic: 'hello',
qos: 0
}, {
topic: 'aaa',
qos: 0
}], function (err) {
t.error(err, 'no error')
broker.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 0
}, function (err) {
t.error(err, 'no error')
})
})
})
const s = connect(setup(broker))
s.outStream.once('data', function (packet) {
t.same(packet, expected, 'packet matches')
})
})
test('subscribe a client programmatically with full packet', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
dup: false,
length: 12,
qos: 0,
retain: false
}
broker.on('client', function (client) {
client.subscribe({
subscriptions: [{
topic: 'hello',
qos: 0
}, {
topic: 'aaa',
qos: 0
}]
}, function (err) {
t.error(err, 'no error')
broker.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 0
}, function (err) {
t.error(err, 'no error')
})
})
})
const s = connect(setup(broker))
s.outStream.once('data', function (packet) {
t.same(packet, expected, 'packet matches')
})
})
test('get message when client connects', function (t) {
t.plan(2)
const client1 = 'gav'
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.on('client', function (client) {
client.subscribe({
subscriptions: [{
topic: '$SYS/+/new/clients',
qos: 0
}]
}, function (err) {
t.error(err, 'no error')
})
})
const s1 = connect(setup(broker), { clientId: client1 })
s1.outStream.on('data', function (packet) {
t.equal(client1, packet.payload.toString())
})
})
test('get message when client disconnects', function (t) {
t.plan(2)
const client1 = 'gav'
const client2 = 'friend'
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.on('client', function (client) {
if (client.id === client1) {
client.subscribe({
subscriptions: [{
topic: '$SYS/+/disconnect/clients',
qos: 0
}]
}, function (err) {
t.error(err, 'no error')
})
} else {
client.close()
}
})
const s1 = connect(setup(broker), { clientId: client1 })
connect(setup(broker), { clientId: client2 })
s1.outStream.on('data', function (packet) {
t.equal(client2, packet.payload.toString())
})
})
test('should not receive a message on negated subscription', function (t) {
t.plan(4)
const broker = aedes()
t.teardown(broker.close.bind(broker))
broker.authorizeSubscribe = function (client, sub, callback) {
callback(null, null)
}
broker.on('client', function (client) {
broker.publish({
topic: 'hello',
payload: Buffer.from('world'),
qos: 0,
retain: true
}, function (err) {
t.error(err, 'no error')
client.subscribe([{
topic: 'hello',
qos: 0
},
{
topic: 'hello',
qos: 0
}], function (err) {
t.error(err, 'no error')
})
})
})
broker.on('subscribe', function (subs) {
t.pass(subs.length, 1, 'Should dedupe subs')
t.pass(subs[0].qos, 128, 'Qos should be 128 (Fail)')
})
const s = connect(setup(broker))
s.outStream.once('data', function (packet) {
t.fail('Packet should not be received')
})
})
test('programmatically add custom subscribe', function (t) {
t.plan(6)
const broker = aedes({ clientId: 'my-client-xyz-7' })
t.teardown(broker.close.bind(broker))
const s = connect(setup(broker), { clientId: 'my-client-xyz-7' })
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
qos: 0,
retain: false,
length: 12,
dup: false
}
const deliverP = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
qos: 0,
retain: false,
dup: false,
clientId: 'my-client-xyz-7'
}
subscribe(t, s, 'hello', 0, function () {
broker.subscribe('hello', deliver, function () {
t.pass('subscribed')
})
s.outStream.on('data', function (packet) {
t.same(packet, expected, 'packet matches')
})
s.inStream.write({
cmd: 'publish',
topic: 'hello',
payload: 'world',
qos: 0,
messageId: 42
})
})
function deliver (packet, cb) {
deliverP.brokerId = s.broker.id
deliverP.brokerCounter = s.broker.counter
t.same(packet, deliverP, 'packet matches')
cb()
}
})
test('custom function in broker.subscribe', function (t) {
t.plan(4)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const s = setup(broker)
const expected = {
cmd: 'publish',
topic: 'hello',
payload: Buffer.from('world'),
qos: 1,
retain: false,
dup: false,
messageId: undefined,
clientId: 'my-client-xyz-6'
}
connect(s, { clientId: 'my-client-xyz-6' }, function () {
broker.subscribe('hello', deliver, function () {
t.pass('subscribed')
})
s.inStream.write({
cmd: 'publish',
topic: 'hello',
payload: 'world',
qos: 1,
messageId: 42
})
})
broker.on('publish', function (packet, client) {
if (client) {
t.equal(packet.topic, 'hello')
t.equal(packet.messageId, 42)
}
})
function deliver (packet, cb) {
expected.brokerId = s.broker.id
expected.brokerCounter = s.broker.counter
t.same(packet, expected, 'packet matches')
cb()
}
})
test('custom function in broker.unsubscribe', function (t) {
t.plan(3)
const broker = aedes()
t.teardown(broker.close.bind(broker))
const s = noError(setup(broker))
connect(s, {}, function () {
broker.subscribe('hello', deliver, function () {
t.pass('subscribed')
broker.unsubscribe('hello', deliver, function () {
t.pass('unsubscribe')
s.inStream.write({
cmd: 'publish',
topic: 'hello',
payload: 'word',
qos: 1,
messageId: 42
})
})
})
})
broker.on('publish', function (packet, client) {
if (client) {
t.pass('publish')
}
})
function deliver (packet, cb) {
t.fail('should not be called')
cb()
}
})