aedes 0.51.3 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.github/actions/sticky-pr-comment/action.yml +55 -0
  3. package/.github/workflows/benchmark-compare-serial.yml +60 -0
  4. package/.github/workflows/ci.yml +12 -17
  5. package/.release-it.json +18 -0
  6. package/.taprc +15 -6
  7. package/README.md +6 -4
  8. package/aedes.d.ts +0 -6
  9. package/aedes.js +270 -242
  10. package/benchmarks/README.md +33 -0
  11. package/benchmarks/pingpong.js +94 -25
  12. package/benchmarks/receiver.js +77 -0
  13. package/benchmarks/report.js +150 -0
  14. package/benchmarks/runBenchmarks.js +118 -0
  15. package/benchmarks/sender.js +86 -0
  16. package/benchmarks/server.js +19 -18
  17. package/checkVersion.js +20 -0
  18. package/docs/Aedes.md +66 -8
  19. package/docs/Client.md +3 -4
  20. package/docs/Examples.md +39 -22
  21. package/docs/MIGRATION.md +50 -0
  22. package/eslint.config.js +8 -0
  23. package/example.js +51 -40
  24. package/examples/clusters/index.js +28 -23
  25. package/examples/clusters/package.json +10 -6
  26. package/lib/client.js +405 -306
  27. package/lib/handlers/connect.js +42 -38
  28. package/lib/handlers/index.js +9 -11
  29. package/lib/handlers/ping.js +2 -3
  30. package/lib/handlers/puback.js +5 -5
  31. package/lib/handlers/publish.js +29 -14
  32. package/lib/handlers/pubrec.js +9 -17
  33. package/lib/handlers/pubrel.js +34 -25
  34. package/lib/handlers/subscribe.js +47 -43
  35. package/lib/handlers/unsubscribe.js +16 -19
  36. package/lib/qos-packet.js +14 -17
  37. package/lib/utils.js +5 -12
  38. package/lib/write.js +4 -5
  39. package/package.json +139 -136
  40. package/test/auth.js +468 -804
  41. package/test/basic.js +613 -575
  42. package/test/bridge.js +44 -40
  43. package/test/client-pub-sub.js +531 -504
  44. package/test/close_socket_by_other_party.js +137 -102
  45. package/test/connect.js +487 -484
  46. package/test/drain-timeout.js +593 -0
  47. package/test/drain-toxiproxy.js +620 -0
  48. package/test/events.js +173 -145
  49. package/test/helper.js +351 -73
  50. package/test/keep-alive.js +40 -67
  51. package/test/meta.js +257 -210
  52. package/test/not-blocking.js +93 -197
  53. package/test/qos1.js +464 -554
  54. package/test/qos2.js +308 -393
  55. package/test/regr-21.js +39 -21
  56. package/test/require.cjs +22 -0
  57. package/test/retain.js +349 -398
  58. package/test/topics.js +176 -183
  59. package/test/types/aedes.test-d.ts +4 -8
  60. package/test/will.js +310 -428
  61. package/types/instance.d.ts +40 -35
  62. package/types/packet.d.ts +10 -10
  63. package/.coveralls.yml +0 -1
  64. package/benchmarks/bombing.js +0 -34
  65. package/benchmarks/bombingQoS1.js +0 -36
  66. package/benchmarks/throughputCounter.js +0 -23
  67. package/benchmarks/throughputCounterQoS1.js +0 -33
  68. package/types/.eslintrc.json +0 -47
package/test/basic.js CHANGED
@@ -1,28 +1,63 @@
1
- 'use strict'
1
+ import { test } from 'node:test'
2
+ import { once } from 'node:events'
3
+ import { Duplex } from 'node:stream'
4
+ import {
5
+ brokerPublish,
6
+ checkNoPacket,
7
+ connect,
8
+ createAndConnect,
9
+ delay,
10
+ nextPacket,
11
+ setup,
12
+ subscribe,
13
+ subscribeMultiple,
14
+ withTimeout
15
+ } from './helper.js'
16
+ import defaultExport, { Aedes } from '../aedes.js'
17
+ import write from '../lib/write.js'
18
+
19
+ test('test Aedes constructor', (t) => {
20
+ t.plan(1)
21
+ const aedes = new Aedes()
22
+ t.assert.equal(aedes instanceof Aedes, true, 'Aedes constructor works')
23
+ })
24
+
25
+ test('test warning on default export', (t) => {
26
+ t.plan(1)
27
+ t.assert.throws(defaultExport, 'received expected error')
28
+ })
2
29
 
3
- const { test } = require('tap')
4
- const eos = require('end-of-stream')
5
- const { setup, connect, subscribe, subscribeMultiple, noError } = require('./helper')
6
- const aedes = require('../')
7
- const proxyquire = require('proxyquire')
30
+ test('test aedes.createBroker', async (t) => {
31
+ t.plan(1)
32
+ const broker = await Aedes.createBroker()
33
+ t.after(() => broker.close())
34
+ await connect(setup(broker))
35
+ t.assert.ok(true, 'connected')
36
+ })
8
37
 
9
- test('test aedes.createBroker', function (t) {
38
+ test('test non-async persistence.setup throws error', async (t) => {
10
39
  t.plan(1)
11
40
 
12
- const broker = aedes.createBroker()
13
- t.teardown(broker.close.bind(broker))
41
+ class P {
42
+ setup () {
43
+ console.log('I am a synchronous setup function')
44
+ }
45
+ }
14
46
 
15
- connect(setup(broker), {}, function () {
16
- t.pass('connected')
17
- })
47
+ const p = new P()
48
+ const broker = new Aedes({ persistence: p })
49
+ t.after(() => broker.close())
50
+ try {
51
+ await broker.listen()
52
+ } catch (_err) {
53
+ t.assert.ok(true, 'receiving expected error')
54
+ }
18
55
  })
19
56
 
20
- test('publish QoS 0', function (t) {
57
+ test('publish QoS 0', async (t) => {
21
58
  t.plan(2)
22
59
 
23
- const s = connect(setup(), { clientId: 'my-client-xyz-5' })
24
- t.teardown(s.broker.close.bind(s.broker))
25
-
60
+ const s = await createAndConnect(t)
26
61
  const expected = {
27
62
  cmd: 'publish',
28
63
  topic: 'hello',
@@ -30,29 +65,31 @@ test('publish QoS 0', function (t) {
30
65
  qos: 0,
31
66
  retain: false,
32
67
  dup: false,
33
- clientId: 'my-client-xyz-5'
68
+ clientId: 'my-client'
34
69
  }
35
70
 
36
- s.broker.mq.on('hello', function (packet, cb) {
37
- expected.brokerId = s.broker.id
38
- expected.brokerCounter = s.broker.counter
39
- t.equal(packet.messageId, undefined, 'MUST not contain a packet identifier in QoS 0')
40
- t.same(packet, expected, 'packet matches')
41
- cb()
42
- })
71
+ await new Promise(resolve => {
72
+ s.broker.mq.on('hello', (packet, cb) => {
73
+ expected.brokerId = s.broker.id
74
+ expected.brokerCounter = s.broker.counter
75
+ t.assert.equal(packet.messageId, undefined, 'MUST not contain a packet identifier in QoS 0')
76
+ t.assert.deepEqual(structuredClone(packet), expected, 'packet matches')
77
+ cb()
78
+ resolve()
79
+ })
43
80
 
44
- s.inStream.write({
45
- cmd: 'publish',
46
- topic: 'hello',
47
- payload: 'world'
81
+ s.inStream.write({
82
+ cmd: 'publish',
83
+ topic: 'hello',
84
+ payload: 'world'
85
+ })
48
86
  })
49
87
  })
50
88
 
51
- test('messageId shoud reset to 1 if it reached 65535', function (t) {
89
+ test('messageId shoud reset to 1 if it reached 65535', async (t) => {
52
90
  t.plan(7)
53
91
 
54
- const s = connect(setup())
55
- t.teardown(s.broker.close.bind(s.broker))
92
+ const s = await createAndConnect(t)
56
93
 
57
94
  const publishPacket = {
58
95
  cmd: 'publish',
@@ -62,63 +99,75 @@ test('messageId shoud reset to 1 if it reached 65535', function (t) {
62
99
  messageId: 42
63
100
  }
64
101
  let count = 0
65
- s.broker.on('clientReady', function (client) {
66
- subscribe(t, s, 'hello', 1, function () {
67
- client._nextId = 65535
68
- s.outStream.on('data', function (packet) {
69
- if (packet.cmd === 'puback') {
70
- t.equal(packet.messageId, 42)
71
- }
72
- if (packet.cmd === 'publish') {
73
- t.equal(packet.messageId, count++ === 0 ? 65535 : 1)
74
- }
75
- })
76
- s.inStream.write(publishPacket)
77
- s.inStream.write(publishPacket)
78
- })
79
- })
102
+ let pubacks = 0
103
+ let published = 0
104
+
105
+ const [client] = await once(s.broker, 'clientReady')
106
+ await subscribe(t, s, 'hello', 1)
107
+ client._nextId = 65535
108
+
109
+ const checkResults = async () => {
110
+ for await (const packet of s.outStream) {
111
+ if (packet.cmd === 'puback') {
112
+ t.assert.equal(packet.messageId, 42)
113
+ pubacks++
114
+ }
115
+ if (packet.cmd === 'publish') {
116
+ t.assert.equal(packet.messageId, count++ === 0 ? 65535 : 1)
117
+ published++
118
+ }
119
+ if (pubacks === 2 && published === 2) {
120
+ break
121
+ }
122
+ }
123
+ }
124
+ const publishPackets = () => {
125
+ s.inStream.write(publishPacket)
126
+ s.inStream.write(publishPacket)
127
+ }
128
+ // run parallel
129
+ await Promise.all([publishPackets(), checkResults()])
80
130
  })
81
131
 
82
- test('publish empty topic throws error', function (t) {
83
- t.plan(1)
84
-
85
- const s = connect(setup())
86
- t.teardown(s.broker.close.bind(s.broker))
87
-
132
+ test('publish empty topic throws error', async (t) => {
133
+ t.plan(2)
134
+ const s = await createAndConnect(t)
88
135
  s.inStream.write({
89
136
  cmd: 'publish',
90
137
  topic: '',
91
138
  payload: 'world'
92
139
  })
93
140
 
94
- s.broker.on('clientError', function (client, err) {
95
- t.pass('should emit error')
96
- })
141
+ const [client, err] = await once(s.broker, 'clientError')
142
+ t.assert.ok(client)
143
+ t.assert.ok(err, 'should emit error')
97
144
  })
98
145
 
99
- test('publish to $SYS topic throws error', function (t) {
100
- t.plan(1)
101
-
102
- const s = connect(setup())
103
- t.teardown(s.broker.close.bind(s.broker))
146
+ test('publish to $SYS topic throws error', async (t) => {
147
+ t.plan(2)
104
148
 
149
+ const s = await createAndConnect(t)
105
150
  s.inStream.write({
106
151
  cmd: 'publish',
107
152
  topic: '$SYS/not/allowed',
108
153
  payload: 'world'
109
154
  })
110
155
 
111
- s.broker.on('clientError', function (client, err) {
112
- t.pass('should emit error')
113
- })
156
+ const [client, err] = await once(s.broker, 'clientError')
157
+ t.assert.ok(client)
158
+ t.assert.ok(err, 'should emit error')
114
159
  })
115
160
 
116
- ;[{ qos: 0, clean: false }, { qos: 0, clean: true }, { qos: 1, clean: false }, { qos: 1, clean: true }].forEach(function (ele) {
117
- test('subscribe a single topic in QoS ' + ele.qos + ' [clean=' + ele.clean + ']', function (t) {
161
+ for (const ele of [
162
+ { qos: 0, clean: false },
163
+ { qos: 0, clean: true },
164
+ { qos: 1, clean: false },
165
+ { qos: 1, clean: true }
166
+ ]) {
167
+ test('subscribe a single topic in QoS ' + ele.qos + ' [clean=' + ele.clean + ']', async (t) => {
118
168
  t.plan(5)
119
169
 
120
- const s = connect(setup(), { clean: ele.clean })
121
- t.teardown(s.broker.close.bind(s.broker))
170
+ const s = await createAndConnect(t, { connect: { clean: ele.clean } })
122
171
 
123
172
  const expected = {
124
173
  cmd: 'publish',
@@ -129,56 +178,62 @@ test('publish to $SYS topic throws error', function (t) {
129
178
  qos: 0,
130
179
  retain: false
131
180
  }
132
- const expectedSubs = ele.clean ? null : [{ topic: 'hello', qos: ele.qos, rh: undefined, rap: undefined, nl: undefined }]
133
-
134
- subscribe(t, s, 'hello', ele.qos, function () {
135
- s.outStream.once('data', function (packet) {
136
- t.same(packet, expected, 'packet matches')
137
- })
181
+ const expectedSubs = ele.clean
182
+ ? []
183
+ : [
184
+ {
185
+ topic: 'hello',
186
+ qos: ele.qos,
187
+ rh: undefined,
188
+ rap: undefined,
189
+ nl: undefined
190
+ }
191
+ ]
138
192
 
139
- s.broker.persistence.subscriptionsByClient(s.client, function (_, subs) {
140
- t.same(subs, expectedSubs)
141
- })
193
+ await subscribe(t, s, 'hello', ele.qos)
194
+ const subs = await s.broker.persistence.subscriptionsByClient(s.client)
195
+ t.assert.deepEqual(subs, expectedSubs, 'subs match')
142
196
 
143
- s.broker.publish({
144
- cmd: 'publish',
145
- topic: 'hello',
146
- payload: 'world'
147
- })
197
+ s.broker.publish({
198
+ cmd: 'publish',
199
+ topic: 'hello',
200
+ payload: 'world'
148
201
  })
202
+
203
+ const packet = await nextPacket(s)
204
+ t.assert.deepEqual(structuredClone(packet), expected, 'packet matches')
149
205
  })
150
- })
206
+ }
151
207
 
152
208
  // Catch invalid packet writeToStream errors
153
- test('return write errors to callback', function (t) {
209
+ test('return write errors to callback', async (t) => {
154
210
  t.plan(1)
155
211
 
156
- const write = proxyquire('../lib/write.js', {
157
- 'mqtt-packet': {
158
- writeToStream: () => {
159
- throw Error('error')
160
- }
161
- }
162
- })
163
-
164
212
  const client = {
165
213
  conn: {
166
- writable: true
214
+ writable: true,
215
+ write: () => { throw new Error('error') }
167
216
  },
168
217
  connecting: true
169
218
  }
170
-
171
- write(client, {}, function (err) {
172
- t.equal(err.message, 'packet received not valid', 'should return the error to callback')
219
+ await new Promise(resolve => {
220
+ write(client, {}, err => {
221
+ t.assert.equal(err.message, 'packet received not valid', 'should return the error to callback')
222
+ resolve()
223
+ })
173
224
  })
174
225
  })
175
226
 
176
- ;[{ qos: 0, clean: false }, { qos: 0, clean: true }, { qos: 1, clean: false }, { qos: 1, clean: true }].forEach(function (ele) {
177
- test('subscribe multipe topics in QoS ' + ele.qos + ' [clean=' + ele.clean + ']', function (t) {
227
+ for (const ele of [
228
+ { qos: 0, clean: false },
229
+ { qos: 0, clean: true },
230
+ { qos: 1, clean: false },
231
+ { qos: 1, clean: true }
232
+ ]) {
233
+ test('subscribe multipe topics in QoS ' + ele.qos + ' [clean=' + ele.clean + ']', async (t) => {
178
234
  t.plan(5)
179
235
 
180
- const s = connect(setup(), { clean: ele.clean })
181
- t.teardown(s.broker.close.bind(s.broker))
236
+ const s = await createAndConnect(t, { connect: { clean: ele.clean } })
182
237
 
183
238
  const expected = {
184
239
  cmd: 'publish',
@@ -189,86 +244,76 @@ test('return write errors to callback', function (t) {
189
244
  qos: 0,
190
245
  retain: false
191
246
  }
192
- const subs = [
247
+
248
+ const subsToSubscribe = [
193
249
  { topic: 'hello', qos: ele.qos, rh: undefined, rap: undefined, nl: undefined },
194
250
  { topic: 'world', qos: ele.qos, rh: undefined, rap: undefined, nl: undefined }
195
251
  ]
196
- const expectedSubs = ele.clean ? null : subs
197
-
198
- subscribeMultiple(t, s, subs, [ele.qos, ele.qos], function () {
199
- s.outStream.on('data', function (packet) {
200
- t.same(packet, expected, 'packet matches')
201
- })
252
+ const expectedSubs = ele.clean ? [] : subsToSubscribe
202
253
 
203
- s.broker.persistence.subscriptionsByClient(s.client, function (_, saveSubs) {
204
- t.same(saveSubs, expectedSubs)
205
- })
206
-
207
- s.broker.publish({
208
- cmd: 'publish',
209
- topic: 'hello',
210
- payload: 'world'
211
- })
254
+ await subscribeMultiple(t, s, subsToSubscribe, [ele.qos, ele.qos])
255
+ const subs = await s.broker.persistence.subscriptionsByClient(s.client)
256
+ t.assert.deepEqual(subs, expectedSubs, 'subs match')
257
+ s.broker.publish({
258
+ cmd: 'publish',
259
+ topic: 'hello',
260
+ payload: 'world'
212
261
  })
262
+ const packet = await nextPacket(s)
263
+ t.assert.deepEqual(structuredClone(packet), expected, 'packet matches')
213
264
  })
214
- })
265
+ }
215
266
 
216
- test('does not die badly on connection error', function (t) {
217
- t.plan(3)
267
+ test('does not die badly on connection error', async (t) => {
268
+ t.plan(6)
269
+ const s = await createAndConnect(t)
218
270
 
219
- const s = connect(setup())
220
- t.teardown(s.broker.close.bind(s.broker))
271
+ await subscribe(t, s, 'hello', 0)
221
272
 
222
- s.inStream.write({
223
- cmd: 'subscribe',
224
- messageId: 42,
225
- subscriptions: [{
226
- topic: 'hello',
227
- qos: 0
228
- }]
229
- })
230
-
231
- s.broker.on('clientError', function (client, err) {
232
- t.ok(client, 'client is passed')
233
- t.ok(err, 'err is passed')
234
- })
273
+ const clientError = async () => {
274
+ const [client, err] = await once(s.broker, 'clientError')
275
+ t.assert.ok(client, 'client is passed')
276
+ t.assert.ok(err, 'err is passed')
277
+ }
235
278
 
236
- s.outStream.on('data', function (packet) {
279
+ const serverWorking = new Promise(resolve => {
237
280
  s.conn.destroy()
238
- s.broker.publish({
239
- cmd: 'publish',
240
- topic: 'hello',
241
- payload: Buffer.from('world')
242
- }, function () {
243
- t.pass('calls the callback')
281
+ setImmediate(() => {
282
+ s.broker.publish({
283
+ cmd: 'publish',
284
+ topic: 'hello',
285
+ payload: Buffer.from('world')
286
+ }, () => {
287
+ t.assert.ok(true, 'calls the callback')
288
+ resolve()
289
+ })
244
290
  })
245
291
  })
292
+ await Promise.all([clientError(), serverWorking])
246
293
  })
247
294
 
248
295
  // Guarded in mqtt-packet
249
- test('subscribe should have messageId', function (t) {
296
+ test('subscribe should have messageId', async (t) => {
250
297
  t.plan(1)
298
+ const s = await createAndConnect(t)
251
299
 
252
- const s = connect(setup())
253
- t.teardown(s.broker.close.bind(s.broker))
254
-
255
- s.inStream.write({
256
- cmd: 'subscribe',
257
- subscriptions: [{
258
- topic: 'hello',
259
- qos: 0
260
- }]
261
- })
262
- s.broker.on('connectionError', function (client, err) {
263
- t.ok(err.message, 'Invalid messageId')
264
- })
300
+ try {
301
+ s.inStream.write({
302
+ cmd: 'subscribe',
303
+ subscriptions: [{
304
+ topic: 'hello',
305
+ qos: 0
306
+ }]
307
+ })
308
+ } catch (err) {
309
+ t.assert.ok(err.message, 'Invalid messageId')
310
+ }
265
311
  })
266
312
 
267
- test('subscribe with messageId 0 should return suback', function (t) {
313
+ test('subscribe with messageId 0 should return suback', async (t) => {
268
314
  t.plan(1)
269
315
 
270
- const s = connect(setup())
271
- t.teardown(s.broker.close.bind(s.broker))
316
+ const s = await createAndConnect(t)
272
317
 
273
318
  s.inStream.write({
274
319
  cmd: 'subscribe',
@@ -278,64 +323,63 @@ test('subscribe with messageId 0 should return suback', function (t) {
278
323
  }],
279
324
  messageId: 0
280
325
  })
281
- s.outStream.once('data', function (packet) {
282
- t.same(packet, {
283
- cmd: 'suback',
284
- messageId: 0,
285
- dup: false,
286
- length: 3,
287
- qos: 0,
288
- retain: false,
289
- granted: [
290
- 0
291
- ]
292
- }, 'packet matches')
293
- })
294
- })
295
326
 
296
- test('unsubscribe', function (t) {
297
- t.plan(5)
327
+ const packet = await nextPacket(s)
328
+ t.assert.deepEqual(structuredClone(packet), {
329
+ cmd: 'suback',
330
+ messageId: 0,
331
+ dup: false,
332
+ length: 3,
333
+ qos: 0,
334
+ retain: false,
335
+ payload: null,
336
+ topic: null,
337
+ granted: [
338
+ 0
339
+ ]
340
+ }, 'packet matches')
341
+ })
298
342
 
299
- const s = noError(connect(setup()), t)
300
- t.teardown(s.broker.close.bind(s.broker))
343
+ test('unsubscribe', async (t) => {
344
+ t.plan(6)
301
345
 
302
- subscribe(t, s, 'hello', 0, function () {
303
- s.inStream.write({
304
- cmd: 'unsubscribe',
305
- messageId: 43,
306
- unsubscriptions: ['hello']
307
- })
346
+ const s = await createAndConnect(t)
347
+ await subscribe(t, s, 'hello', 0)
348
+ s.inStream.write({
349
+ cmd: 'unsubscribe',
350
+ messageId: 43,
351
+ unsubscriptions: ['hello']
352
+ })
308
353
 
309
- s.outStream.once('data', function (packet) {
310
- t.same(packet, {
311
- cmd: 'unsuback',
312
- messageId: 43,
313
- dup: false,
314
- length: 2,
315
- qos: 0,
316
- retain: false
317
- }, 'packet matches')
318
-
319
- s.outStream.on('data', function (packet) {
320
- t.fail('packet received')
321
- })
354
+ const packet = await nextPacket(s)
355
+ t.assert.deepEqual(structuredClone(packet), {
356
+ cmd: 'unsuback',
357
+ messageId: 43,
358
+ dup: false,
359
+ length: 2,
360
+ qos: 0,
361
+ retain: false,
362
+ payload: null,
363
+ topic: null
364
+ }, 'packet matches')
322
365
 
323
- s.broker.publish({
324
- cmd: 'publish',
325
- topic: 'hello',
326
- payload: 'world'
327
- }, function () {
328
- t.pass('publish finished')
329
- })
366
+ await new Promise(resolve => {
367
+ s.broker.publish({
368
+ cmd: 'publish',
369
+ topic: 'hello',
370
+ payload: 'world'
371
+ }, () => {
372
+ t.assert.ok(true, 'publish finished')
373
+ resolve()
330
374
  })
331
375
  })
376
+ await checkNoPacket(t, s)
332
377
  })
333
378
 
334
- test('unsubscribe without subscribe', function (t) {
379
+ test('unsubscribe without subscribe', async (t) => {
335
380
  t.plan(1)
336
381
 
337
- const s = noError(connect(setup()), t)
338
- t.teardown(s.broker.close.bind(s.broker))
382
+ const s = await createAndConnect(t)
339
383
 
340
384
  s.inStream.write({
341
385
  cmd: 'unsubscribe',
@@ -343,306 +387,323 @@ test('unsubscribe without subscribe', function (t) {
343
387
  unsubscriptions: ['hello']
344
388
  })
345
389
 
346
- s.outStream.once('data', function (packet) {
347
- t.same(packet, {
348
- cmd: 'unsuback',
349
- messageId: 43,
350
- dup: false,
351
- length: 2,
352
- qos: 0,
353
- retain: false
354
- }, 'packet matches')
355
- })
390
+ const packet = await nextPacket(s)
391
+ t.assert.deepEqual(structuredClone(packet), {
392
+ cmd: 'unsuback',
393
+ messageId: 43,
394
+ dup: false,
395
+ length: 2,
396
+ qos: 0,
397
+ retain: false,
398
+ payload: null,
399
+ topic: null
400
+ }, 'packet matches')
356
401
  })
357
402
 
358
- test('unsubscribe on disconnect for a clean=true client', function (t) {
359
- t.plan(6)
403
+ test('unsubscribe on disconnect for a clean=true client', async (t) => {
404
+ t.plan(7)
360
405
 
361
- const opts = { clean: true }
362
- const s = connect(setup(), opts)
363
- t.teardown(s.broker.close.bind(s.broker))
406
+ const opts = { connect: { clean: true } }
407
+ const s = await createAndConnect(t, opts)
364
408
 
365
- subscribe(t, s, 'hello', 0, function () {
366
- s.conn.destroy(null, function () {
367
- t.pass('closed streams')
368
- })
369
- s.outStream.on('data', function () {
370
- t.fail('should not receive any more messages')
371
- })
372
- s.broker.once('unsubscribe', function () {
373
- t.pass('should emit unsubscribe')
374
- })
375
- s.broker.publish({
409
+ await subscribe(t, s, 'hello', 0)
410
+ s.conn.destroy(null)
411
+ t.assert.equal(s.conn.destroyed, true, 'closed streams')
412
+
413
+ const emittedUnsubscribe = async () => {
414
+ await once(s.broker, 'unsubscribe')
415
+ t.assert.ok(true, 'should emit unsubscribe')
416
+ }
417
+
418
+ const publishPacket = async () => {
419
+ await brokerPublish(s, {
376
420
  cmd: 'publish',
377
421
  topic: 'hello',
378
422
  payload: Buffer.from('world')
379
- }, function () {
380
- t.pass('calls the callback')
381
423
  })
382
- })
424
+ t.assert.ok(true, 'calls the callback')
425
+ }
426
+ // run parallel
427
+ await Promise.all([checkNoPacket(t, s), emittedUnsubscribe(), publishPacket()])
383
428
  })
384
429
 
385
- test('unsubscribe on disconnect for a clean=false client', function (t) {
386
- t.plan(5)
430
+ test('unsubscribe on disconnect for a clean=false client', async (t) => {
431
+ t.plan(7)
387
432
 
388
- const opts = { clean: false }
389
- const s = connect(setup(), opts)
390
- t.teardown(s.broker.close.bind(s.broker))
433
+ const opts = { connect: { clean: false } }
434
+ const s = await createAndConnect(t, opts)
391
435
 
392
- subscribe(t, s, 'hello', 0, function () {
393
- s.conn.destroy(null, function () {
394
- t.pass('closed streams')
395
- })
396
- s.outStream.on('data', function () {
397
- t.fail('should not receive any more messages')
398
- })
399
- s.broker.once('unsubscribe', function () {
400
- t.fail('should not emit unsubscribe')
401
- })
402
- s.broker.publish({
436
+ await subscribe(t, s, 'hello', 0)
437
+ s.conn.destroy(null, () => {
438
+ t.assert.ok(true, 'closed streams')
439
+ })
440
+
441
+ const emittedNoUnsubscribe = async () => {
442
+ const result = await withTimeout(once(s.broker, 'unsubscribe'), 10, null)
443
+ t.assert.equal(result, null, 'should not emit unsubscribe')
444
+ }
445
+ const publishPacket = async () => {
446
+ await brokerPublish(s, {
403
447
  cmd: 'publish',
404
448
  topic: 'hello',
405
449
  payload: Buffer.from('world')
406
- }, function () {
407
- t.pass('calls the callback')
408
450
  })
409
- })
451
+ t.assert.ok(true, 'calls the callback')
452
+ }
453
+ // run parallel
454
+ await Promise.all([checkNoPacket(t, s), emittedNoUnsubscribe(), publishPacket()])
410
455
  })
411
456
 
412
- test('disconnect', function (t) {
457
+ test('disconnect', async (t) => {
413
458
  t.plan(1)
414
459
 
415
- const s = noError(connect(setup()), t)
416
- t.teardown(s.broker.close.bind(s.broker))
460
+ const s = await createAndConnect(t)
417
461
 
418
- s.broker.on('clientDisconnect', function () {
419
- t.pass('closed stream')
420
- })
462
+ const checkDisconnect = async () => {
463
+ await once(s.broker, 'clientDisconnect')
464
+ t.assert.ok(true, 'closed stream')
465
+ }
421
466
 
422
- s.inStream.write({
423
- cmd: 'disconnect'
424
- })
467
+ const disconnect = () => {
468
+ s.inStream.write({
469
+ cmd: 'disconnect'
470
+ })
471
+ }
472
+ // run parallel
473
+ await Promise.all([checkDisconnect(), disconnect()])
425
474
  })
426
475
 
427
- test('disconnect client on wrong cmd', function (t) {
476
+ test('disconnect client on wrong cmd', async (t) => {
428
477
  t.plan(1)
429
478
 
430
- const s = noError(connect(setup()), t)
431
- t.teardown(s.broker.close.bind(s.broker))
432
-
433
- s.broker.on('clientDisconnect', function () {
434
- t.pass('closed stream')
435
- })
436
-
437
- s.broker.on('clientReady', function (c) {
438
- // don't use stream write here because it will throw an error on mqtt_packet genetete
439
- c._parser.emit('packet', { cmd: 'pippo' })
440
- })
479
+ const s = await createAndConnect(t)
480
+ s.client._parser.emit('packet', { cmd: 'pippo' })
481
+ await once(s.broker, 'clientDisconnect')
482
+ t.assert.ok(true, 'closed stream')
441
483
  })
442
484
 
443
- test('client closes', function (t) {
485
+ test('client closes', async (t) => {
444
486
  t.plan(5)
445
487
 
446
- const broker = aedes()
447
- const client = noError(connect(setup(broker), { clientId: 'abcde' }))
448
- broker.on('clientReady', function () {
449
- const brokerClient = broker.clients.abcde
450
- t.equal(brokerClient.connected, true, 'client connected')
451
- eos(client.conn, t.pass.bind(t, 'client closes'))
488
+ const { broker, client } = await createAndConnect(t, { connect: { clientId: 'abcde' } })
489
+
490
+ await once(broker, 'clientReady')
491
+ const brokerClient = broker.clients.abcde
492
+ t.assert.equal(brokerClient.connected, true, 'client connected')
493
+
494
+ const clientClosed = async () => {
495
+ await once(client.conn, 'close')
496
+ t.assert.ok(true, 'client disconnected')
497
+ }
498
+ const closeAll = new Promise(resolve => {
452
499
  setImmediate(() => {
453
- brokerClient.close(function () {
454
- t.equal(broker.clients.abcde, undefined, 'client instance is removed')
455
- })
456
- t.equal(brokerClient.connected, false, 'client disconnected')
457
- broker.close(function (err) {
458
- t.error(err, 'no error')
500
+ brokerClient.close(() => {
501
+ t.assert.equal(broker.clients.abcde, undefined, 'client instance is removed')
502
+ t.assert.equal(brokerClient.connected, false, 'client disconnected')
503
+ broker.close((err) => {
504
+ t.assert.ok(!err, 'no error')
505
+ resolve()
506
+ })
459
507
  })
460
508
  })
461
509
  })
510
+ // run parallel
511
+ await Promise.all([clientClosed(), closeAll])
462
512
  })
463
513
 
464
- test('broker closes', function (t) {
514
+ test('broker closes', async (t) => {
465
515
  t.plan(4)
466
516
 
467
- const broker = aedes()
468
- const client = noError(connect(setup(broker), {
469
- clientId: 'abcde'
470
- }, function () {
471
- eos(client.conn, t.pass.bind(t, 'client closes'))
472
- broker.close(function (err) {
473
- t.error(err, 'no error')
474
- t.ok(broker.closed)
475
- t.equal(broker.clients.abcde, undefined, 'client instance is removed')
517
+ const { broker, client } = await createAndConnect(t, { connect: { clientId: 'abcde' } })
518
+
519
+ const clientClosed = async () => {
520
+ await once(client.conn, 'close')
521
+ t.assert.ok(true, 'client closes')
522
+ }
523
+
524
+ const brokerClose = new Promise(resolve => {
525
+ broker.close((err) => {
526
+ t.assert.ok(!err, 'no error')
527
+ t.assert.ok(broker.closed)
528
+ t.assert.equal(broker.clients.abcde, undefined, 'client instance is removed')
529
+ resolve()
476
530
  })
477
- }))
531
+ })
532
+ // run parallel
533
+ await Promise.all([clientClosed(), brokerClose])
478
534
  })
479
535
 
480
- test('broker closes gracefully', function (t) {
536
+ test('broker closes gracefully', async (t) => {
481
537
  t.plan(7)
482
538
 
483
- const broker = aedes()
484
- const client1 = noError(connect(setup(broker), {
485
- }, function () {
486
- const client2 = noError(connect(setup(broker), {
487
- }, function () {
488
- t.equal(broker.connectedClients, 2, '2 connected clients')
489
- eos(client1.conn, t.pass.bind(t, 'client1 closes'))
490
- eos(client2.conn, t.pass.bind(t, 'client2 closes'))
491
- broker.close(function (err) {
492
- t.error(err, 'no error')
493
- t.ok(broker.mq.closed, 'broker mq closes')
494
- t.ok(broker.closed, 'broker closes')
495
- t.equal(broker.connectedClients, 0, 'no connected clients')
496
- })
497
- }))
498
- }))
539
+ const broker = await Aedes.createBroker()
540
+ t.after(() => broker.close())
541
+ const s1 = setup(broker)
542
+ await connect(s1, { connect: { clientId: 'my-client-1' } })
543
+ const s2 = setup(broker)
544
+ await connect(s2, { connect: { clientId: 'my-client-2' } })
545
+ t.assert.equal(broker.connectedClients, 2, '2 connected clients')
546
+ const client1Closed = async () => {
547
+ await once(s1.client.conn, 'close')
548
+ t.assert.ok(true, 'client1 closes')
549
+ }
550
+ const client2Closed = async () => {
551
+ await once(s2.client.conn, 'close')
552
+ t.assert.ok(true, 'client2 closes')
553
+ }
554
+ const brokerClose = new Promise(resolve => {
555
+ broker.close((err) => {
556
+ t.assert.ok(!err, 'no error')
557
+ t.assert.ok(broker.mq.closed, 'broker mq closes')
558
+ t.assert.ok(broker.closed, 'broker closes')
559
+ t.assert.equal(broker.connectedClients, 0, 'no connected clients')
560
+ resolve()
561
+ })
562
+ })
563
+ // run parallel
564
+ await Promise.all([client1Closed(), client2Closed(), brokerClose])
499
565
  })
500
566
 
501
- test('testing other event', function (t) {
567
+ test('testing other event', async (t) => {
502
568
  t.plan(1)
503
569
 
504
- const broker = aedes()
505
- t.teardown(broker.close.bind(broker))
570
+ const broker = await Aedes.createBroker()
571
+ t.after(() => broker.close())
506
572
 
507
- const client = setup(broker)
573
+ // can't use default setup because of errothandling on duplexPair
574
+ const server = new Duplex()
575
+ broker.handle(server)
508
576
 
509
- broker.on('connectionError', function (client, error) {
510
- t.notOk(client.id, null)
511
- })
512
- client.conn.emit('error', 'Connect not yet arrived')
577
+ const connectionError = async () => {
578
+ const [client] = await once(broker, 'connectionError')
579
+ t.assert.ok(!client.id, 'client not present')
580
+ }
581
+ const emitError = () => {
582
+ server.emit('error', 'Connect not yet arrived')
583
+ }
584
+
585
+ // run parallel
586
+ await Promise.all([
587
+ connectionError(),
588
+ emitError()
589
+ ])
513
590
  })
514
591
 
515
- test('connect without a clientId for MQTT 3.1.1', function (t) {
592
+ test('connect without a clientId for MQTT 3.1.1', async (t) => {
516
593
  t.plan(1)
517
594
 
518
- const s = setup()
519
- t.teardown(s.broker.close.bind(s.broker))
520
-
521
- s.inStream.write({
522
- cmd: 'connect',
523
- protocolId: 'MQTT',
524
- protocolVersion: 4,
525
- clean: true,
526
- keepalive: 0
527
- })
528
-
529
- s.outStream.on('data', function (packet) {
530
- t.same(packet, {
531
- cmd: 'connack',
532
- returnCode: 0,
533
- length: 2,
534
- qos: 0,
535
- retain: false,
536
- dup: false,
537
- topic: null,
538
- payload: null,
539
- sessionPresent: false
540
- }, 'successful connack')
541
- })
595
+ const broker = await Aedes.createBroker()
596
+ t.after(() => broker.close())
597
+ const s1 = setup(broker)
598
+ const packet = await connect(s1, { noClientId: true })
599
+ t.assert.deepEqual(structuredClone(packet), {
600
+ cmd: 'connack',
601
+ returnCode: 0,
602
+ length: 2,
603
+ qos: 0,
604
+ retain: false,
605
+ dup: false,
606
+ topic: null,
607
+ payload: null,
608
+ sessionPresent: false
609
+ }, 'successful connack')
542
610
  })
543
611
 
544
- test('disconnect existing client with the same clientId', function (t) {
612
+ test('disconnect existing client with the same clientId', async (t) => {
545
613
  t.plan(2)
546
614
 
547
- const broker = aedes()
548
- t.teardown(broker.close.bind(broker))
549
-
550
- const c1 = connect(setup(broker), {
551
- clientId: 'abcde'
552
- }, function () {
553
- eos(c1.conn, function () {
554
- t.pass('first client disconnected')
555
- })
556
-
557
- connect(setup(broker), {
558
- clientId: 'abcde'
559
- }, function () {
560
- t.pass('second client connected')
561
- })
562
- })
615
+ const broker = await Aedes.createBroker()
616
+ t.after(() => broker.close())
617
+ const s1 = setup(broker)
618
+ await connect(s1, { clientId: 'abcde' })
619
+ const s2 = setup(broker)
620
+ await connect(s2, { clientId: 'abcde' })
621
+ t.assert.equal(s2.conn.closed, false, 's2 is still connected')
622
+ t.assert.equal(s1.conn.closed, true, 's1 has been disconnected')
563
623
  })
564
624
 
565
- test('disconnect if another broker connects the same clientId', function (t) {
566
- t.plan(2)
567
-
568
- const broker = aedes()
569
- t.teardown(broker.close.bind(broker))
570
-
571
- const c1 = connect(setup(broker), {
572
- clientId: 'abcde'
573
- }, function () {
574
- eos(c1.conn, function () {
575
- t.pass('disconnect first client')
576
- })
625
+ test('disconnect if another broker connects the same clientId', async (t) => {
626
+ t.plan(1)
577
627
 
578
- broker.publish({
579
- topic: '$SYS/anotherBroker/new/clients',
580
- payload: Buffer.from('abcde')
581
- }, function () {
582
- t.pass('second client connects to another broker')
583
- })
628
+ const s = await createAndConnect(t, { connect: { clientId: 'abcde' } })
629
+ await brokerPublish(s, {
630
+ topic: '$SYS/anotherBroker/new/clients',
631
+ payload: Buffer.from('abcde')
584
632
  })
633
+ t.assert.equal(s.conn.closed, true, 'client has been disconnected')
585
634
  })
586
635
 
587
- test('publish to $SYS/broker/new/clients', function (t) {
636
+ test('publish to $SYS/broker/new/clients', async (t) => {
588
637
  t.plan(1)
589
638
 
590
- const broker = aedes()
591
- t.teardown(broker.close.bind(broker))
639
+ const broker = await Aedes.createBroker()
640
+ t.after(() => broker.close())
641
+ const s = setup(broker)
592
642
 
593
- broker.mq.on('$SYS/' + broker.id + '/new/clients', function (packet, done) {
594
- t.equal(packet.payload.toString(), 'abcde', 'clientId matches')
595
- done()
596
- })
597
-
598
- connect(setup(broker), {
599
- clientId: 'abcde'
643
+ const brokerBroadcasted = new Promise(resolve => {
644
+ broker.mq.on(`$SYS/${broker.id}/new/clients`, (packet, done) => {
645
+ t.assert.equal(packet.payload.toString(), 'abcde', 'clientId matches')
646
+ resolve()
647
+ done()
648
+ })
600
649
  })
650
+ // run parallel
651
+ await Promise.all([
652
+ brokerBroadcasted,
653
+ connect(s, { connect: { clientId: 'abcde' } })
654
+ ])
601
655
  })
602
656
 
603
- test('publish to $SYS/broker/new/subsribers and $SYS/broker/new/unsubsribers', function (t) {
657
+ test('publish to $SYS/broker/new/subscribers and $SYS/broker/new/unsubscribers', async (t) => {
604
658
  t.plan(7)
605
- const broker = aedes()
606
- t.teardown(broker.close.bind(broker))
659
+
660
+ const subscriber = await createAndConnect(t, { connect: { clean: false, clientId: 'abcde' } })
661
+ const broker = subscriber.broker
607
662
 
608
663
  const sub = {
609
664
  topic: 'hello',
610
665
  qos: 0
611
666
  }
612
667
 
613
- broker.mq.on('$SYS/' + broker.id + '/new/subscribes', function (packet, done) {
614
- const payload = JSON.parse(packet.payload.toString())
615
- t.equal(payload.clientId, 'abcde', 'clientId matches')
616
- t.same(payload.subs, [sub], 'subscriptions matches')
617
- done()
668
+ const subscribeBroadcasted = new Promise(resolve => {
669
+ broker.mq.on(`$SYS/${broker.id}/new/subscribes`, (packet, done) => {
670
+ const payload = JSON.parse(packet.payload.toString())
671
+ t.assert.equal(payload.clientId, 'abcde', 'clientId matches')
672
+ t.assert.deepEqual(payload.subs, [sub], 'subscriptions matches')
673
+ resolve()
674
+ done()
675
+ })
618
676
  })
619
677
 
620
- broker.mq.on('$SYS/' + broker.id + '/new/unsubscribes', function (packet, done) {
621
- const payload = JSON.parse(packet.payload.toString())
622
- t.equal(payload.clientId, 'abcde', 'clientId matches')
623
- t.same(payload.subs, [sub.topic], 'unsubscriptions matches')
624
- done()
678
+ const unsubscribeBroadcasted = new Promise(resolve => {
679
+ broker.mq.on(`$SYS/${broker.id}/new/unsubscribes`, (packet, done) => {
680
+ const payload = JSON.parse(packet.payload.toString())
681
+ t.assert.equal(payload.clientId, 'abcde', 'clientId matches')
682
+ t.assert.deepEqual(payload.subs, [sub.topic], 'unsubscriptions matches')
683
+ resolve()
684
+ done()
685
+ })
625
686
  })
626
687
 
627
- const subscriber = connect(setup(broker), {
628
- clean: false, clientId: 'abcde'
629
- }, function () {
630
- subscribe(t, subscriber, sub.topic, sub.qos, function () {
631
- subscriber.inStream.write({
632
- cmd: 'unsubscribe',
633
- messageId: 43,
634
- unsubscriptions: ['hello']
635
- })
688
+ const subUnsub = async () => {
689
+ await subscribe(t, subscriber, sub.topic, sub.qos)
690
+ subscriber.inStream.write({
691
+ cmd: 'unsubscribe',
692
+ messageId: 43,
693
+ unsubscriptions: ['hello']
636
694
  })
637
- })
695
+ }
696
+ // run parallel
697
+ await Promise.all([
698
+ subscribeBroadcasted,
699
+ unsubscribeBroadcasted,
700
+ subUnsub()
701
+ ])
638
702
  })
639
703
 
640
- test('restore QoS 0 subscriptions not clean', function (t) {
704
+ test('restore QoS 0 subscriptions not clean', async (t) => {
641
705
  t.plan(5)
642
706
 
643
- const broker = aedes()
644
- t.teardown(broker.close.bind(broker))
645
-
646
707
  const expected = {
647
708
  cmd: 'publish',
648
709
  topic: 'hello',
@@ -653,68 +714,57 @@ test('restore QoS 0 subscriptions not clean', function (t) {
653
714
  retain: false
654
715
  }
655
716
 
656
- let subscriber = connect(setup(broker), {
657
- clean: false, clientId: 'abcde'
658
- }, function () {
659
- subscribe(t, subscriber, 'hello', 0, function () {
660
- subscriber.inStream.end()
661
-
662
- const publisher = connect(setup(broker), {
663
- }, function () {
664
- subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
665
- t.equal(connect.sessionPresent, true, 'session present is set to true')
666
- publisher.inStream.write({
667
- cmd: 'publish',
668
- topic: 'hello',
669
- payload: 'world',
670
- qos: 0
671
- })
672
- })
673
- subscriber.outStream.once('data', function (packet) {
674
- t.same(packet, expected, 'packet must match')
675
- })
676
- })
677
- })
717
+ const subscriber = await createAndConnect(t, { connect: { clean: false, clientId: 'abcde' } })
718
+ const broker = subscriber.broker
719
+ await subscribe(t, subscriber, 'hello', 0)
720
+ // hangup
721
+ subscriber.inStream.end()
722
+
723
+ // start second connection
724
+ const publisher = setup(broker)
725
+ await connect(publisher)
726
+ // start third connection
727
+ const subscriber2 = setup(broker)
728
+ const connack = await connect(subscriber2, { connect: { clean: false, clientId: 'abcde' } })
729
+ t.assert.equal(connack.sessionPresent, true, 'session present is set to true')
730
+ publisher.inStream.write({
731
+ cmd: 'publish',
732
+ topic: 'hello',
733
+ payload: 'world',
734
+ qos: 0
678
735
  })
736
+ const packet = await nextPacket(subscriber2)
737
+ t.assert.deepEqual(structuredClone(packet), expected, 'packet must match')
679
738
  })
680
739
 
681
- test('do not restore QoS 0 subscriptions when clean', function (t) {
682
- t.plan(5)
683
-
684
- const broker = aedes()
685
- t.teardown(broker.close.bind(broker))
740
+ test('do not restore QoS 0 subscriptions when clean', async (t) => {
741
+ t.plan(6)
686
742
 
687
- let subscriber = connect(setup(broker), {
688
- clean: true, clientId: 'abcde'
689
- }, function () {
690
- subscribe(t, subscriber, 'hello', 0, function () {
691
- subscriber.inStream.end()
692
- subscriber.broker.persistence.subscriptionsByClient(broker.clients.abcde, function (_, subs, client) {
693
- t.equal(subs, null, 'no previous subscriptions restored')
694
- })
695
- const publisher = connect(setup(broker), {
696
- }, function () {
697
- subscriber = connect(setup(broker), {
698
- clean: true, clientId: 'abcde'
699
- }, function (connect) {
700
- t.equal(connect.sessionPresent, false, 'session present is set to false')
701
- publisher.inStream.write({
702
- cmd: 'publish',
703
- topic: 'hello',
704
- payload: 'world',
705
- qos: 0
706
- })
707
- })
708
- subscriber.outStream.once('data', function (packet) {
709
- t.fail('packet received')
710
- })
711
- })
712
- })
743
+ const subscriber = await createAndConnect(t, { connect: { clean: true, clientId: 'abcde' } })
744
+ const broker = subscriber.broker
745
+ await subscribe(t, subscriber, 'hello', 0)
746
+ // hangup
747
+ subscriber.inStream.end()
748
+
749
+ const subs = await subscriber.broker.persistence.subscriptionsByClient(broker.clients.abcde)
750
+ t.assert.deepEqual(subs, [], 'no previous subscriptions restored')
751
+
752
+ const publisher = setup(broker)
753
+ await connect(publisher)
754
+ const subscriber2 = setup(broker)
755
+ const connack = await connect(subscriber2, { connect: { clean: true, clientId: 'abcde' } })
756
+ t.assert.equal(connack.sessionPresent, false, 'session present is set to false')
757
+ publisher.inStream.write({
758
+ cmd: 'publish',
759
+ topic: 'hello',
760
+ payload: 'world',
761
+ qos: 0
713
762
  })
763
+ await checkNoPacket(t, subscriber2)
714
764
  })
715
765
 
716
- test('double sub does not double deliver', function (t) {
717
- t.plan(7)
766
+ test('double sub does not double deliver', async (t) => {
767
+ t.plan(8)
718
768
 
719
769
  const expected = {
720
770
  cmd: 'publish',
@@ -725,30 +775,24 @@ test('double sub does not double deliver', function (t) {
725
775
  qos: 0,
726
776
  retain: false
727
777
  }
728
- const s = connect(setup(), {
729
- }, function () {
730
- subscribe(t, s, 'hello', 0, function () {
731
- subscribe(t, s, 'hello', 0, function () {
732
- s.outStream.once('data', function (packet) {
733
- t.same(packet, expected, 'packet matches')
734
- s.outStream.on('data', function () {
735
- t.fail('double deliver')
736
- })
737
- })
738
778
 
739
- s.broker.publish({
740
- cmd: 'publish',
741
- topic: 'hello',
742
- payload: 'world'
743
- })
744
- })
745
- })
779
+ const s = await createAndConnect(t, { connect: { clean: false, clientId: 'abcde' } })
780
+ await subscribe(t, s, 'hello', 0)
781
+ await subscribe(t, s, 'hello', 0)
782
+
783
+ s.broker.publish({
784
+ cmd: 'publish',
785
+ topic: 'hello',
786
+ payload: 'world'
746
787
  })
747
- t.teardown(s.broker.close.bind(s.broker))
788
+
789
+ const packet = await nextPacket(s)
790
+ t.assert.deepEqual(structuredClone(packet), expected, 'packet matches')
791
+ await checkNoPacket(t, s)
748
792
  })
749
793
 
750
- test('overlapping sub does not double deliver', function (t) {
751
- t.plan(7)
794
+ test('overlapping sub does not double deliver', async (t) => {
795
+ t.plan(8)
752
796
 
753
797
  const expected = {
754
798
  cmd: 'publish',
@@ -759,107 +803,101 @@ test('overlapping sub does not double deliver', function (t) {
759
803
  qos: 0,
760
804
  retain: false
761
805
  }
762
- const s = connect(setup(), {
763
- }, function () {
764
- subscribe(t, s, 'hello', 0, function () {
765
- subscribe(t, s, 'hello/#', 0, function () {
766
- s.outStream.once('data', function (packet) {
767
- t.same(packet, expected, 'packet matches')
768
- s.outStream.on('data', function () {
769
- t.fail('double deliver')
770
- })
771
- })
806
+ const s = await createAndConnect(t, { connect: { clean: false, clientId: 'abcde' } })
807
+ await subscribe(t, s, 'hello', 0)
808
+ await subscribe(t, s, 'hello/#', 0)
772
809
 
773
- s.broker.publish({
774
- cmd: 'publish',
775
- topic: 'hello',
776
- payload: 'world'
777
- })
778
- })
779
- })
810
+ s.broker.publish({
811
+ cmd: 'publish',
812
+ topic: 'hello',
813
+ payload: 'world'
780
814
  })
781
- t.teardown(s.broker.close.bind(s.broker))
815
+
816
+ const packet = await nextPacket(s)
817
+ t.assert.deepEqual(structuredClone(packet), expected, 'packet matches')
818
+ await checkNoPacket(t, s)
782
819
  })
783
820
 
784
- test('clear drain', function (t) {
785
- t.plan(4)
821
+ test('clear drain', async (t) => {
822
+ t.plan(5)
786
823
 
787
- const s = connect(setup(), {
788
- }, function () {
789
- subscribe(t, s, 'hello', 0, function () {
790
- // fake a busy socket
791
- s.conn.write = function (chunk, enc, cb) {
792
- return false
793
- }
824
+ const s = await createAndConnect(t, { broker: { drainTimeout: 0 } }) // Disable timeout to test old behavior
825
+ await subscribe(t, s, 'hello', 0)
794
826
 
795
- s.broker.publish({
796
- cmd: 'publish',
797
- topic: 'hello',
798
- payload: 'world'
799
- }, function () {
800
- t.pass('callback called')
801
- })
827
+ // fake a busy socket
828
+ let written = false // 1 packet requires multiple writes, just count 1
829
+ s.client.conn.write = (chunk, enc, cb) => {
830
+ if (!written) {
831
+ t.assert.ok(true, 'write called')
832
+ written = true
833
+ }
834
+ return false
835
+ }
802
836
 
803
- s.conn.destroy()
837
+ const publish = async () => {
838
+ await brokerPublish(s, {
839
+ cmd: 'publish',
840
+ topic: 'hello',
841
+ payload: 'world'
804
842
  })
805
- })
806
843
 
807
- t.teardown(s.broker.close.bind(s.broker))
844
+ t.assert.ok(true, 'published packet')
845
+ }
846
+
847
+ // run parallel
848
+ await Promise.all([
849
+ publish(),
850
+ s.client.conn.destroy()
851
+ ])
808
852
  })
809
853
 
810
- test('id option', function (t) {
854
+ test('id option', (t) => {
811
855
  t.plan(2)
812
856
 
813
- const broker1 = aedes()
814
-
815
- setup(broker1).conn.destroy()
816
- t.ok(broker1.id, 'broker gets random id when id option not set')
857
+ const broker1 = new Aedes()
858
+ t.assert.ok(broker1.id, 'broker gets random id when id option not set')
817
859
 
818
- const broker2 = aedes({ id: 'abc' })
819
- setup(broker2).conn.destroy()
820
- t.equal(broker2.id, 'abc', 'broker id equals id option when set')
821
-
822
- t.teardown(() => {
823
- broker1.close()
824
- broker2.close()
825
- })
860
+ const broker2 = new Aedes({ id: 'abc' })
861
+ t.assert.equal(broker2.id, 'abc', 'broker id equals id option when set')
826
862
  })
827
863
 
828
- test('not duplicate client close when client error occurs', function (t) {
829
- t.plan(1)
864
+ test('not duplicate client close when client error occurs', async (t) => {
865
+ t.plan(2)
830
866
 
831
- const broker = aedes()
832
- t.teardown(broker.close.bind(broker))
867
+ const s = await createAndConnect(t)
868
+ const checkDoubleDrain = async () => {
869
+ await once(s.client.conn, 'drain')
870
+ t.assert.ok(true, 'client closed ok')
871
+ const result = await withTimeout(once(s.client.conn, 'drain'), 10, ['timeout'])
872
+ t.assert.equal(result, 'timeout', 'no double client close calls')
873
+ }
833
874
 
834
- connect(setup(broker))
835
- broker.on('client', function (client) {
836
- client.conn.on('drain', () => {
837
- t.pass('client closed ok')
838
- })
839
- client.close()
840
- // add back to test if there is duplicated close() call
841
- client.conn.on('drain', () => {
842
- t.fail('double client close calls')
843
- })
844
- })
875
+ // run parallel
876
+ await Promise.all([
877
+ checkDoubleDrain(),
878
+ s.client.close()
879
+ ])
845
880
  })
846
881
 
847
- test('not duplicate client close when double close() called', function (t) {
848
- t.plan(1)
882
+ test('not duplicate client close when double close() called', async (t) => {
883
+ t.plan(2)
849
884
 
850
- const broker = aedes()
851
- t.teardown(broker.close.bind(broker))
885
+ const s = await createAndConnect(t)
886
+ const checkDoubleDrain = async () => {
887
+ await once(s.client.conn, 'drain')
888
+ t.assert.ok(true, 'client closed ok')
889
+ const result = await withTimeout(once(s.client.conn, 'drain'), 10, ['timeout'])
890
+ t.assert.equal(result, 'timeout', 'no double client close calls')
891
+ }
892
+ const doubleClose = async () => {
893
+ s.client.close()
894
+ await delay(1)
895
+ s.client.close()
896
+ }
852
897
 
853
- connect(setup(broker))
854
- broker.on('clientReady', function (client) {
855
- client.conn.on('drain', () => {
856
- t.pass('client closed ok')
857
- })
858
- client.close()
859
- // add back to test if there is duplicated close() call
860
- client.conn.on('drain', () => {
861
- t.fail('double execute client close function')
862
- })
863
- client.close()
864
- })
898
+ // run parallel
899
+ await Promise.all([
900
+ checkDoubleDrain(),
901
+ doubleClose()
902
+ ])
865
903
  })