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

714 lines
17 KiB
JavaScript

'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')
})
})
})
})
})
})
})
})