amqplib 1.0.5 → 1.0.7
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.
- package/CHANGELOG.md +7 -0
- package/Makefile +1 -1
- package/README.md +51 -0
- package/lib/callback_model.js +13 -14
- package/lib/channel.js +24 -21
- package/lib/channel_model.js +5 -5
- package/lib/connection.js +9 -8
- package/lib/safe_emit.js +13 -0
- package/package.json +1 -1
- package/test/callback_api.test.js +13 -23
- package/test/channel.test.js +307 -1
- package/test/channel_api.test.js +51 -0
- package/test/connection.test.js +351 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Change log for amqplib
|
|
2
2
|
|
|
3
|
+
## v1.0.7
|
|
4
|
+
- Fix `update-secret-ok` event not being forwarded by `ChannelModel` and `CallbackModel` (fixes #849)
|
|
5
|
+
- Add `handler-error` event to connections and channels. If a user-supplied event handler (e.g. `connection.on('close', ...)`, `channel.on('error', ...)`, `channel.on('delivery', ...)` etc.) throws a synchronous error, amqplib will emit a `handler-error` event on the same emitter with the thrown error and the name of the event whose handler threw — provided a `handler-error` listener is registered. If no `handler-error` listener is registered, behaviour is unchanged from previous versions. Note: in previous versions, errors thrown in connection `close` event handlers were silently swallowed; errors thrown in channel event handlers (other than `delivery`/`return`) would kill the channel and possibly the connection (fixes #334).
|
|
6
|
+
|
|
7
|
+
## v1.0.6
|
|
8
|
+
- Fix channel.get() not invoking callback with error on channel close; previously only an error event was emitted (fixes #832). **Note:** if you use the callback API, ensure your channel.get() callbacks handle errors — they will now be invoked in error cases where previously they were not. If you use the promise API, the returned promise now rejects with a proper Error object (with .code, .classId and .methodId properties) rather than a raw close frame.
|
|
9
|
+
|
|
3
10
|
## v1.0.5
|
|
4
11
|
- Fix ConfirmChannel callbacks silently dropped on channel close when some publishes had no callback (fixes #191)
|
|
5
12
|
|
package/Makefile
CHANGED
|
@@ -2,7 +2,7 @@ RABBITMQ_SRC_VERSION=v3.12.13
|
|
|
2
2
|
JSON=amqp-rabbitmq-0.9.1.json
|
|
3
3
|
AMQP_JSON=https://raw.githubusercontent.com/rabbitmq/rabbitmq-server/$(RABBITMQ_SRC_VERSION)/deps/rabbitmq_codegen/$(JSON)
|
|
4
4
|
|
|
5
|
-
NODEJS_VERSIONS='18.
|
|
5
|
+
NODEJS_VERSIONS='18.20.0' '20.10.0' '22.14.0' '24.7.0'
|
|
6
6
|
|
|
7
7
|
UGLIFY=./node_modules/.bin/uglifyjs
|
|
8
8
|
|
package/README.md
CHANGED
|
@@ -43,10 +43,16 @@ const queue = 'tasks';
|
|
|
43
43
|
amqplib.connect('amqp://localhost', (err, conn) => {
|
|
44
44
|
if (err) throw err;
|
|
45
45
|
|
|
46
|
+
conn.on('error', (err) => { console.error('Connection error:', err); });
|
|
47
|
+
conn.on('handler-error', (err, event) => { console.error(`Uncaught exception in connection ${event} listener:`, err); });
|
|
48
|
+
|
|
46
49
|
// Listener
|
|
47
50
|
conn.createChannel((err, ch2) => {
|
|
48
51
|
if (err) throw err;
|
|
49
52
|
|
|
53
|
+
ch2.on('error', (err) => { console.error('Channel error:', err); });
|
|
54
|
+
ch2.on('handler-error', (err, event) => { console.error(`Uncaught exception in channel ${event} listener:`, err); });
|
|
55
|
+
|
|
50
56
|
ch2.assertQueue(queue);
|
|
51
57
|
|
|
52
58
|
ch2.consume(queue, (msg) => {
|
|
@@ -63,6 +69,8 @@ amqplib.connect('amqp://localhost', (err, conn) => {
|
|
|
63
69
|
conn.createChannel((err, ch1) => {
|
|
64
70
|
if (err) throw err;
|
|
65
71
|
|
|
72
|
+
ch1.on('error', (err) => { console.error('Channel error:', err); });
|
|
73
|
+
ch1.on('handler-error', (err, event) => { console.error(`Uncaught exception in channel ${event} listener:`, err); });
|
|
66
74
|
ch1.assertQueue(queue);
|
|
67
75
|
|
|
68
76
|
setInterval(() => {
|
|
@@ -80,8 +88,12 @@ const amqplib = require('amqplib');
|
|
|
80
88
|
(async () => {
|
|
81
89
|
const queue = 'tasks';
|
|
82
90
|
const conn = await amqplib.connect('amqp://localhost');
|
|
91
|
+
conn.on('error', (err) => { console.error('Connection error:', err); });
|
|
92
|
+
conn.on('handler-error', (err, event) => { console.error(`Uncaught exception in connection ${event} listener:`, err); });
|
|
83
93
|
|
|
84
94
|
const ch1 = await conn.createChannel();
|
|
95
|
+
ch1.on('error', (err) => { console.error('Channel error:', err); });
|
|
96
|
+
ch1.on('handler-error', (err, event) => { console.error(`Uncaught exception in channel ${event} listener:`, err); });
|
|
85
97
|
await ch1.assertQueue(queue);
|
|
86
98
|
|
|
87
99
|
// Listener
|
|
@@ -96,6 +108,8 @@ const amqplib = require('amqplib');
|
|
|
96
108
|
|
|
97
109
|
// Sender
|
|
98
110
|
const ch2 = await conn.createChannel();
|
|
111
|
+
ch2.on('error', (err) => { console.error('Channel error:', err); });
|
|
112
|
+
ch2.on('handler-error', (err, event) => { console.error(`Uncaught exception in channel ${event} listener:`, err); });
|
|
99
113
|
|
|
100
114
|
setInterval(() => {
|
|
101
115
|
ch2.sendToQueue(queue, Buffer.from('something to do'));
|
|
@@ -104,6 +118,43 @@ const amqplib = require('amqplib');
|
|
|
104
118
|
|
|
105
119
|
```
|
|
106
120
|
|
|
121
|
+
## Error handling in event handlers
|
|
122
|
+
|
|
123
|
+
If a user-supplied event handler throws a synchronous error, the throw will
|
|
124
|
+
propagate into amqplib internals. Depending on where in the call stack it
|
|
125
|
+
escapes, this can silently swallow the error, or close the channel or
|
|
126
|
+
connection.
|
|
127
|
+
|
|
128
|
+
To avoid this, register a `handler-error` listener on the connection and on
|
|
129
|
+
each channel. If a listener is present, amqplib will catch any throw from a
|
|
130
|
+
user event handler and deliver it there instead of letting it propagate
|
|
131
|
+
internally. The listener receives the thrown error and the name of the event
|
|
132
|
+
whose handler threw.
|
|
133
|
+
|
|
134
|
+
Note that `handler-error` is not a replacement for the `error` event.
|
|
135
|
+
The `error` event is emitted by amqplib itself when the connection or channel
|
|
136
|
+
encounters a protocol-level error. The `handler-error` event is only emitted
|
|
137
|
+
when *your own* event listener throws.
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
const connection = await amqp.connect('amqp://localhost');
|
|
141
|
+
|
|
142
|
+
connection.on('error', (err) => { /* handle protocol errors */ });
|
|
143
|
+
connection.on('handler-error', (err, event) => {
|
|
144
|
+
console.error(`Uncaught exception in connection ${event} listener:`, err);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const channel = await connection.createChannel();
|
|
148
|
+
|
|
149
|
+
channel.on('error', (err) => { /* handle protocol errors */ });
|
|
150
|
+
channel.on('handler-error', (err, event) => {
|
|
151
|
+
console.error(`Uncaught exception in channel ${event} listener:`, err);
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
If no `handler-error` listener is registered, behaviour is unchanged from
|
|
156
|
+
previous versions.
|
|
157
|
+
|
|
107
158
|
## Running tests
|
|
108
159
|
|
|
109
160
|
npm test
|
package/lib/callback_model.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
const defs = require('./defs');
|
|
2
2
|
const EventEmitter = require('node:events');
|
|
3
|
-
const BaseChannel = require('./channel').BaseChannel;
|
|
4
|
-
const acceptMessage = require('./channel').acceptMessage;
|
|
5
3
|
const Args = require('./api_args');
|
|
4
|
+
const { BaseChannel, acceptMessage, convertCloseFrameToError } = require('./channel');
|
|
6
5
|
|
|
7
6
|
class CallbackModel extends EventEmitter {
|
|
8
7
|
constructor(connection) {
|
|
9
8
|
super();
|
|
10
9
|
this.connection = connection;
|
|
11
|
-
['error', 'close', 'blocked', 'unblocked'].forEach((ev) => {
|
|
10
|
+
['error', 'close', 'blocked', 'unblocked', 'update-secret-ok'].forEach((ev) => {
|
|
12
11
|
connection.on(ev, this.emit.bind(this, ev));
|
|
13
12
|
});
|
|
14
13
|
}
|
|
@@ -179,17 +178,17 @@ class Channel extends BaseChannel {
|
|
|
179
178
|
const fields = Args.get(queue, options);
|
|
180
179
|
const cb = callbackWrapper(this, cb0);
|
|
181
180
|
this.sendOrEnqueue(defs.BasicGet, fields, (err, f) => {
|
|
182
|
-
if (err
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
}
|
|
181
|
+
if (err instanceof Error) return cb(err);
|
|
182
|
+
if (err) return cb(convertCloseFrameToError(defs.BasicGet, err));
|
|
183
|
+
if (f.id === defs.BasicGetEmpty) {
|
|
184
|
+
cb(null, false);
|
|
185
|
+
} else if (f.id === defs.BasicGetOk) {
|
|
186
|
+
this.handleMessage = acceptMessage((m) => {
|
|
187
|
+
m.fields = f.fields;
|
|
188
|
+
cb(null, m);
|
|
189
|
+
});
|
|
190
|
+
} else {
|
|
191
|
+
cb(new Error(`Unexpected response to BasicGet: ${inspect(f)}`));
|
|
193
192
|
}
|
|
194
193
|
});
|
|
195
194
|
return this;
|
package/lib/channel.js
CHANGED
|
@@ -7,6 +7,7 @@ const EventEmitter = require('node:events');
|
|
|
7
7
|
const fmt = require('node:util').format;
|
|
8
8
|
const IllegalOperationError = require('./error').IllegalOperationError;
|
|
9
9
|
const stackCapture = require('./error').stackCapture;
|
|
10
|
+
const safeEmit = require('./safe_emit');
|
|
10
11
|
|
|
11
12
|
class Channel extends EventEmitter {
|
|
12
13
|
constructor(connection) {
|
|
@@ -116,19 +117,7 @@ class Channel extends EventEmitter {
|
|
|
116
117
|
else if (err instanceof Error) return cb(err);
|
|
117
118
|
// A close frame will be given if this is the RPC awaiting reply
|
|
118
119
|
// and the channel is closed by the server
|
|
119
|
-
else
|
|
120
|
-
// otherwise, it's a close frame
|
|
121
|
-
const closeReason = (err.fields.classId << 16) + err.fields.methodId;
|
|
122
|
-
const e =
|
|
123
|
-
method === closeReason
|
|
124
|
-
? fmt('Operation failed: %s; %s', methodName(method), closeMsg(err))
|
|
125
|
-
: fmt('Channel closed by server: %s', closeMsg(err));
|
|
126
|
-
const closeFrameError = new Error(e);
|
|
127
|
-
closeFrameError.code = err.fields.replyCode;
|
|
128
|
-
closeFrameError.classId = err.fields.classId;
|
|
129
|
-
closeFrameError.methodId = err.fields.methodId;
|
|
130
|
-
return cb(closeFrameError);
|
|
131
|
-
}
|
|
120
|
+
else return cb(convertCloseFrameToError(method, err));
|
|
132
121
|
}
|
|
133
122
|
|
|
134
123
|
this.sendOrEnqueue(method, fields, reply);
|
|
@@ -140,7 +129,7 @@ class Channel extends EventEmitter {
|
|
|
140
129
|
invalidateSend(this, 'Channel closed', capturedStack);
|
|
141
130
|
this.accept = invalidOp('Channel closed', capturedStack);
|
|
142
131
|
this.connection.releaseChannel(this.ch);
|
|
143
|
-
this
|
|
132
|
+
safeEmit(this, 'close');
|
|
144
133
|
}
|
|
145
134
|
|
|
146
135
|
// Stop being able to send and receive methods and content. Used when
|
|
@@ -200,7 +189,7 @@ class Channel extends EventEmitter {
|
|
|
200
189
|
error.classId = defs.info(id).classId;
|
|
201
190
|
error.methodId = defs.info(id).methodId;
|
|
202
191
|
}
|
|
203
|
-
this
|
|
192
|
+
safeEmit(this, 'error', error);
|
|
204
193
|
});
|
|
205
194
|
}
|
|
206
195
|
|
|
@@ -262,7 +251,7 @@ class Channel extends EventEmitter {
|
|
|
262
251
|
}
|
|
263
252
|
|
|
264
253
|
onBufferDrain() {
|
|
265
|
-
this
|
|
254
|
+
safeEmit(this, 'drain');
|
|
266
255
|
}
|
|
267
256
|
|
|
268
257
|
accept(f) {
|
|
@@ -276,12 +265,12 @@ class Channel extends EventEmitter {
|
|
|
276
265
|
|
|
277
266
|
// confirmations, need to do confirm.select first
|
|
278
267
|
case defs.BasicAck:
|
|
279
|
-
return this
|
|
268
|
+
return safeEmit(this, 'ack', f.fields);
|
|
280
269
|
case defs.BasicNack:
|
|
281
|
-
return this
|
|
270
|
+
return safeEmit(this, 'nack', f.fields);
|
|
282
271
|
case defs.BasicCancel:
|
|
283
272
|
// The broker can send this if e.g., the queue is deleted.
|
|
284
|
-
return this
|
|
273
|
+
return safeEmit(this, 'cancel', f.fields);
|
|
285
274
|
|
|
286
275
|
case defs.ChannelClose: {
|
|
287
276
|
// Any remote closure is an error to us. Reject the pending reply
|
|
@@ -299,7 +288,7 @@ class Channel extends EventEmitter {
|
|
|
299
288
|
error.code = f.fields.replyCode;
|
|
300
289
|
error.classId = f.fields.classId;
|
|
301
290
|
error.methodId = f.fields.methodId;
|
|
302
|
-
this
|
|
291
|
+
safeEmit(this, 'error', error);
|
|
303
292
|
|
|
304
293
|
const s = stackCapture(emsg);
|
|
305
294
|
this.toClosed(s);
|
|
@@ -364,7 +353,7 @@ function acceptDeliveryOrReturn(f) {
|
|
|
364
353
|
const fields = f.fields;
|
|
365
354
|
return acceptMessage((message) => {
|
|
366
355
|
message.fields = fields;
|
|
367
|
-
this
|
|
356
|
+
safeEmit(this, event, message);
|
|
368
357
|
});
|
|
369
358
|
}
|
|
370
359
|
|
|
@@ -428,6 +417,19 @@ function acceptMessage(continuation) {
|
|
|
428
417
|
}
|
|
429
418
|
}
|
|
430
419
|
|
|
420
|
+
function convertCloseFrameToError(method, closeFrame) {
|
|
421
|
+
const closeReason = (closeFrame.fields.classId << 16) + closeFrame.fields.methodId;
|
|
422
|
+
const e =
|
|
423
|
+
method === closeReason
|
|
424
|
+
? fmt('Operation failed: %s; %s', methodName(method), closeMsg(closeFrame))
|
|
425
|
+
: fmt('Channel closed by server: %s', closeMsg(closeFrame));
|
|
426
|
+
const error = new Error(e);
|
|
427
|
+
error.code = closeFrame.fields.replyCode;
|
|
428
|
+
error.classId = closeFrame.fields.classId;
|
|
429
|
+
error.methodId = closeFrame.fields.methodId;
|
|
430
|
+
return error;
|
|
431
|
+
}
|
|
432
|
+
|
|
431
433
|
// This adds just a bit more stuff useful for the APIs, but not
|
|
432
434
|
// low-level machinery.
|
|
433
435
|
class BaseChannel extends Channel {
|
|
@@ -471,3 +473,4 @@ class BaseChannel extends Channel {
|
|
|
471
473
|
module.exports.acceptMessage = acceptMessage;
|
|
472
474
|
module.exports.BaseChannel = BaseChannel;
|
|
473
475
|
module.exports.Channel = Channel;
|
|
476
|
+
module.exports.convertCloseFrameToError = convertCloseFrameToError;
|
package/lib/channel_model.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
const EventEmitter = require('node:events');
|
|
2
|
-
const promisify = require('node:util')
|
|
2
|
+
const { promisify } = require('node:util');
|
|
3
3
|
const defs = require('./defs');
|
|
4
|
-
const { BaseChannel } = require('./channel');
|
|
5
|
-
const { acceptMessage } = require('./channel');
|
|
6
4
|
const Args = require('./api_args');
|
|
5
|
+
const { BaseChannel, acceptMessage, convertCloseFrameToError } = require('./channel');
|
|
7
6
|
const { inspect } = require('./format');
|
|
8
7
|
|
|
9
8
|
class ChannelModel extends EventEmitter {
|
|
@@ -11,7 +10,7 @@ class ChannelModel extends EventEmitter {
|
|
|
11
10
|
super();
|
|
12
11
|
this.connection = connection;
|
|
13
12
|
|
|
14
|
-
['error', 'close', 'blocked', 'unblocked'].forEach((ev) => {
|
|
13
|
+
['error', 'close', 'blocked', 'unblocked', 'update-secret-ok'].forEach((ev) => {
|
|
15
14
|
connection.on(ev, this.emit.bind(this, ev));
|
|
16
15
|
});
|
|
17
16
|
}
|
|
@@ -159,7 +158,8 @@ class Channel extends BaseChannel {
|
|
|
159
158
|
const fields = Args.get(queue, options);
|
|
160
159
|
return new Promise((resolve, reject) => {
|
|
161
160
|
this.sendOrEnqueue(defs.BasicGet, fields, (err, f) => {
|
|
162
|
-
if (err) return reject(err);
|
|
161
|
+
if (err instanceof Error) return reject(err);
|
|
162
|
+
if (err) return reject(convertCloseFrameToError(defs.BasicGet, err));
|
|
163
163
|
if (f.id === defs.BasicGetEmpty) {
|
|
164
164
|
return resolve(false);
|
|
165
165
|
} else if (f.id === defs.BasicGetOk) {
|
package/lib/connection.js
CHANGED
|
@@ -17,6 +17,7 @@ const fmt = require('node:util').format;
|
|
|
17
17
|
const PassThrough = require('node:stream').PassThrough;
|
|
18
18
|
const IllegalOperationError = require('./error').IllegalOperationError;
|
|
19
19
|
const stackCapture = require('./error').stackCapture;
|
|
20
|
+
const safeEmit = require('./safe_emit');
|
|
20
21
|
|
|
21
22
|
// High-water mark for channel write buffers, in 'objects' (which are
|
|
22
23
|
// encoded frames as buffers).
|
|
@@ -281,7 +282,7 @@ class Connection extends EventEmitter {
|
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
closeWithError(reason, code, error) {
|
|
284
|
-
this
|
|
285
|
+
safeEmit(this, 'error', error);
|
|
285
286
|
this.closeBecause(reason, code);
|
|
286
287
|
}
|
|
287
288
|
|
|
@@ -290,7 +291,7 @@ class Connection extends EventEmitter {
|
|
|
290
291
|
// forestall any more calls to onSocketError, since we're signed
|
|
291
292
|
// up for `'error'` *and* `'end'`
|
|
292
293
|
this.expectSocketClose = true;
|
|
293
|
-
this
|
|
294
|
+
safeEmit(this, 'error', err);
|
|
294
295
|
const s = stackCapture('Socket error');
|
|
295
296
|
this.toClosed(s, err);
|
|
296
297
|
}
|
|
@@ -345,7 +346,7 @@ class Connection extends EventEmitter {
|
|
|
345
346
|
// This is certainly true now, if it wasn't before
|
|
346
347
|
this.expectSocketClose = true;
|
|
347
348
|
this.stream.end();
|
|
348
|
-
this
|
|
349
|
+
safeEmit(this, 'close', maybeErr);
|
|
349
350
|
}
|
|
350
351
|
|
|
351
352
|
_updateSecret(newSecret, reason, cb) {
|
|
@@ -363,7 +364,7 @@ class Connection extends EventEmitter {
|
|
|
363
364
|
const hb = new Heart(this.heartbeat, this.checkSend.bind(this), this.checkRecv.bind(this));
|
|
364
365
|
hb.on('timeout', () => {
|
|
365
366
|
const hberr = new Error('Heartbeat timeout');
|
|
366
|
-
this
|
|
367
|
+
safeEmit(this, 'error', hberr);
|
|
367
368
|
const s = stackCapture('Heartbeat timeout');
|
|
368
369
|
this.toClosed(s, hberr);
|
|
369
370
|
});
|
|
@@ -585,17 +586,17 @@ function channel0(connection) {
|
|
|
585
586
|
const e = new Error(emsg);
|
|
586
587
|
e.code = f.fields.replyCode;
|
|
587
588
|
if (isFatalError(e)) {
|
|
588
|
-
connection
|
|
589
|
+
safeEmit(connection, 'error', e);
|
|
589
590
|
}
|
|
590
591
|
connection.toClosed(s, e);
|
|
591
592
|
} else if (f.id === defs.ConnectionBlocked) {
|
|
592
593
|
connection.blocked = true;
|
|
593
|
-
connection
|
|
594
|
+
safeEmit(connection, 'blocked', f.fields.reason);
|
|
594
595
|
} else if (f.id === defs.ConnectionUnblocked) {
|
|
595
596
|
connection.blocked = false;
|
|
596
|
-
connection
|
|
597
|
+
safeEmit(connection, 'unblocked');
|
|
597
598
|
} else if (f.id === defs.ConnectionUpdateSecretOk) {
|
|
598
|
-
connection
|
|
599
|
+
safeEmit(connection, 'update-secret-ok');
|
|
599
600
|
} else {
|
|
600
601
|
connection.closeWithError(
|
|
601
602
|
fmt('Unexpected frame on channel 0'),
|
package/lib/safe_emit.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function safeEmit(emitter, event, ...args) {
|
|
2
|
+
try {
|
|
3
|
+
emitter.emit(event, ...args);
|
|
4
|
+
} catch (e) {
|
|
5
|
+
if (emitter.listenerCount('handler-error') > 0) {
|
|
6
|
+
setImmediate(() => emitter.emit('handler-error', e, event));
|
|
7
|
+
} else {
|
|
8
|
+
throw e;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = safeEmit;
|
package/package.json
CHANGED
|
@@ -42,6 +42,17 @@ describe('Callback API', () => {
|
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
it('emits update-secret-ok event', (_t, done) => {
|
|
47
|
+
connect((err, _c) => {
|
|
48
|
+
assert.ifError(err);
|
|
49
|
+
c = _c;
|
|
50
|
+
c.on('update-secret-ok', () => done());
|
|
51
|
+
c.updateSecret(Buffer.from('new secret'), 'no reason', (err) => {
|
|
52
|
+
assert.ifError(err);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
45
56
|
});
|
|
46
57
|
|
|
47
58
|
const channel_test_fn = (method) => {
|
|
@@ -401,39 +412,18 @@ describe('Callback API', () => {
|
|
|
401
412
|
});
|
|
402
413
|
});
|
|
403
414
|
|
|
404
|
-
/*
|
|
405
|
-
When this test was refactored as part of the move to the node test framework
|
|
406
|
-
it failed because only the domain error handler was ever invoked and not the
|
|
407
|
-
channel.get error callback.
|
|
408
|
-
|
|
409
|
-
The original test passed because twice.first (refactored to use util.latch) short circuits on error rather
|
|
410
|
-
than waiting for twice.second to be invoked. I have verified that the pre-refactored
|
|
411
|
-
amqplib does not invoke the channel.get callback when the queue does not exist.
|
|
412
|
-
See lib/callback_model.js
|
|
413
|
-
*/
|
|
414
415
|
error_test('Get from non-queue invokes error', (ch, done, dom) => {
|
|
415
416
|
const decrementLatch = latch(2, () => done());
|
|
416
417
|
dom.on('error', (err) => {
|
|
417
418
|
assert.match(err.message, /404 \(NOT-FOUND\)/);
|
|
418
|
-
decrementLatch(
|
|
419
|
+
decrementLatch();
|
|
419
420
|
});
|
|
420
421
|
ch.get('', {}, (err) => {
|
|
421
422
|
assert.match(err.message, /404 \(NOT-FOUND\)/)
|
|
422
|
-
decrementLatch(
|
|
423
|
+
decrementLatch()
|
|
423
424
|
});
|
|
424
425
|
});
|
|
425
426
|
|
|
426
|
-
/*
|
|
427
|
-
When this test was refactored as part of the move to the node test framework
|
|
428
|
-
it failed because only the domain error handler was ever invoked and not the
|
|
429
|
-
channel.consume error callback. Unlike the above channel.get test,
|
|
430
|
-
channel.consume does invoke the channel.consume callback with an error, but
|
|
431
|
-
the original test did not supply one, mistakenly providing a message handler
|
|
432
|
-
function instead.
|
|
433
|
-
|
|
434
|
-
The original test passed because twice.first (refactored to use util.latch) short circuits on error
|
|
435
|
-
rather than waiting for twice.second to be invoked.
|
|
436
|
-
*/
|
|
437
427
|
error_test('Consume from non-queue invokes error', (ch, done, dom) => {
|
|
438
428
|
const decrementLatch = latch(2, done);
|
|
439
429
|
dom.on('error', (err) => {
|
package/test/channel.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { describe, it } = require('node:test');
|
|
1
|
+
const { describe, it, beforeEach, afterEach } = require('node:test');
|
|
2
2
|
const assert = require('node:assert');
|
|
3
3
|
const promisify = require('node:util').promisify;
|
|
4
4
|
const Channel = require('../lib/channel').Channel;
|
|
@@ -609,4 +609,310 @@ describe('Channel', () => {
|
|
|
609
609
|
cb();
|
|
610
610
|
}));
|
|
611
611
|
});
|
|
612
|
+
|
|
613
|
+
describe('Event handler errors - without handler-error listener', () => {
|
|
614
|
+
let prevUncaughtExceptionListeners;
|
|
615
|
+
|
|
616
|
+
beforeEach(() => {
|
|
617
|
+
prevUncaughtExceptionListeners = process.rawListeners('uncaughtException').slice();
|
|
618
|
+
process.removeAllListeners('uncaughtException');
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
afterEach(() => {
|
|
622
|
+
prevUncaughtExceptionListeners.forEach((h) => process.on('uncaughtException', h));
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('throw in close handler kills the connection without handler-error listener', channelTest((ch, cb, conn) => {
|
|
626
|
+
const expectedErr = new Error('user close handler explodes');
|
|
627
|
+
let connErrorSeen = false;
|
|
628
|
+
// The throw propagates to acceptLoop → frameError → onSocketError → connection.emit('error')
|
|
629
|
+
conn.on('error', (err) => {
|
|
630
|
+
assert.strictEqual(err, expectedErr);
|
|
631
|
+
connErrorSeen = true;
|
|
632
|
+
});
|
|
633
|
+
// The ChannelCloseOk buffered before the kill is flushed by the mux after the
|
|
634
|
+
// stream is ended, producing ERR_STREAM_PUSH_AFTER_EOF. Absorb it and use it
|
|
635
|
+
// as the signal to end the test, since it fires after the conn error.
|
|
636
|
+
process.once('uncaughtException', (err) => {
|
|
637
|
+
assert.strictEqual(err.code, 'ERR_STREAM_PUSH_AFTER_EOF');
|
|
638
|
+
assert.ok(connErrorSeen);
|
|
639
|
+
cb();
|
|
640
|
+
});
|
|
641
|
+
ch.once('error', (err) => { assert.match(err.message, /Channel closed by server/); });
|
|
642
|
+
ch.on('close', () => { throw expectedErr; });
|
|
643
|
+
open(ch);
|
|
644
|
+
}, (send, _wait, cb, ch) => {
|
|
645
|
+
completes(() => {
|
|
646
|
+
send(defs.ChannelClose, {
|
|
647
|
+
replyText: 'Forced close',
|
|
648
|
+
replyCode: defs.constants.CHANNEL_ERROR,
|
|
649
|
+
classId: 0,
|
|
650
|
+
methodId: 0,
|
|
651
|
+
}, ch);
|
|
652
|
+
}, cb);
|
|
653
|
+
}));
|
|
654
|
+
|
|
655
|
+
it('throw in error handler becomes uncaught exception', channelTest((ch, cb) => {
|
|
656
|
+
const expectedErr = new Error('user error handler explodes');
|
|
657
|
+
process.once('uncaughtException', (err) => {
|
|
658
|
+
assert.strictEqual(err, expectedErr);
|
|
659
|
+
cb();
|
|
660
|
+
});
|
|
661
|
+
ch.on('error', () => { throw expectedErr; });
|
|
662
|
+
open(ch);
|
|
663
|
+
}, (send, wait, cb, ch) => {
|
|
664
|
+
send(defs.ChannelClose, {
|
|
665
|
+
replyText: 'Forced close',
|
|
666
|
+
replyCode: defs.constants.CHANNEL_ERROR,
|
|
667
|
+
classId: 0,
|
|
668
|
+
methodId: 0,
|
|
669
|
+
}, ch);
|
|
670
|
+
wait(defs.ChannelCloseOk)().then(cb, cb);
|
|
671
|
+
}));
|
|
672
|
+
|
|
673
|
+
it('throw in drain handler becomes uncaught exception', channelTest((ch, cb) => {
|
|
674
|
+
const expectedErr = new Error('user drain handler explodes');
|
|
675
|
+
process.once('uncaughtException', (err) => {
|
|
676
|
+
assert.strictEqual(err, expectedErr);
|
|
677
|
+
cb();
|
|
678
|
+
});
|
|
679
|
+
ch.on('drain', () => { throw expectedErr; });
|
|
680
|
+
// Call outside a Promise chain so the throw becomes an uncaughtException
|
|
681
|
+
open(ch).then(() => setImmediate(() => ch.onBufferDrain()));
|
|
682
|
+
}, (_send, _wait, cb) => { cb(); }));
|
|
683
|
+
|
|
684
|
+
it('throw in ack handler becomes uncaught exception', channelTest((ch, cb) => {
|
|
685
|
+
const expectedErr = new Error('user ack handler explodes');
|
|
686
|
+
process.once('uncaughtException', (err) => {
|
|
687
|
+
assert.strictEqual(err, expectedErr);
|
|
688
|
+
cb();
|
|
689
|
+
});
|
|
690
|
+
ch.on('ack', () => { throw expectedErr; });
|
|
691
|
+
open(ch);
|
|
692
|
+
}, (send, _wait, cb, ch) => {
|
|
693
|
+
completes(() => {
|
|
694
|
+
send(defs.BasicAck, { deliveryTag: 1, multiple: false }, ch);
|
|
695
|
+
}, cb);
|
|
696
|
+
}));
|
|
697
|
+
|
|
698
|
+
it('throw in nack handler becomes uncaught exception', channelTest((ch, cb) => {
|
|
699
|
+
const expectedErr = new Error('user nack handler explodes');
|
|
700
|
+
process.once('uncaughtException', (err) => {
|
|
701
|
+
assert.strictEqual(err, expectedErr);
|
|
702
|
+
cb();
|
|
703
|
+
});
|
|
704
|
+
ch.on('nack', () => { throw expectedErr; });
|
|
705
|
+
open(ch);
|
|
706
|
+
}, (send, _wait, cb, ch) => {
|
|
707
|
+
completes(() => {
|
|
708
|
+
send(defs.BasicNack, { deliveryTag: 1, multiple: false, requeue: false }, ch);
|
|
709
|
+
}, cb);
|
|
710
|
+
}));
|
|
711
|
+
|
|
712
|
+
it('throw in cancel handler becomes uncaught exception', channelTest((ch, cb) => {
|
|
713
|
+
const expectedErr = new Error('user cancel handler explodes');
|
|
714
|
+
process.once('uncaughtException', (err) => {
|
|
715
|
+
assert.strictEqual(err, expectedErr);
|
|
716
|
+
cb();
|
|
717
|
+
});
|
|
718
|
+
ch.on('cancel', () => { throw expectedErr; });
|
|
719
|
+
open(ch);
|
|
720
|
+
}, (send, _wait, cb, ch) => {
|
|
721
|
+
completes(() => {
|
|
722
|
+
send(defs.BasicCancel, { consumerTag: 'tag', nowait: false }, ch);
|
|
723
|
+
}, cb);
|
|
724
|
+
}));
|
|
725
|
+
|
|
726
|
+
it('throw in delivery handler closes channel with error', channelTest((ch, cb) => {
|
|
727
|
+
// acceptMessageFrame catches the throw and routes it to closeWithError,
|
|
728
|
+
// which fires channel 'error' then 'close' — it does NOT become an uncaughtException.
|
|
729
|
+
const expectedErr = new Error('user delivery handler explodes');
|
|
730
|
+
const decrementLatch = latch(2, cb);
|
|
731
|
+
ch.on('error', (err) => {
|
|
732
|
+
assert.strictEqual(err, expectedErr);
|
|
733
|
+
decrementLatch();
|
|
734
|
+
});
|
|
735
|
+
ch.on('close', decrementLatch);
|
|
736
|
+
ch.on('delivery', () => { throw expectedErr; });
|
|
737
|
+
open(ch);
|
|
738
|
+
}, (send, wait, cb, ch) => {
|
|
739
|
+
send(defs.BasicDeliver, DELIVER_FIELDS, ch, Buffer.from('hello'));
|
|
740
|
+
wait(defs.ChannelClose)()
|
|
741
|
+
.then(() => send(defs.ChannelCloseOk, {}, ch))
|
|
742
|
+
.then(cb, cb);
|
|
743
|
+
}));
|
|
744
|
+
|
|
745
|
+
it('throw in return handler closes channel with error', channelTest((ch, cb) => {
|
|
746
|
+
// acceptMessageFrame catches the throw and routes it to closeWithError.
|
|
747
|
+
const expectedErr = new Error('user return handler explodes');
|
|
748
|
+
const decrementLatch = latch(2, cb);
|
|
749
|
+
ch.on('error', (err) => {
|
|
750
|
+
assert.strictEqual(err, expectedErr);
|
|
751
|
+
decrementLatch();
|
|
752
|
+
});
|
|
753
|
+
ch.on('close', decrementLatch);
|
|
754
|
+
ch.on('return', () => { throw expectedErr; });
|
|
755
|
+
open(ch);
|
|
756
|
+
}, (send, wait, cb, ch) => {
|
|
757
|
+
send(defs.BasicReturn, DELIVER_FIELDS, ch, Buffer.from('hello'));
|
|
758
|
+
wait(defs.ChannelClose)()
|
|
759
|
+
.then(() => send(defs.ChannelCloseOk, {}, ch))
|
|
760
|
+
.then(cb, cb);
|
|
761
|
+
}));
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
describe('Event handler errors - with handler-error listener', () => {
|
|
765
|
+
let prevUncaughtExceptionListeners;
|
|
766
|
+
|
|
767
|
+
beforeEach(() => {
|
|
768
|
+
prevUncaughtExceptionListeners = process.rawListeners('uncaughtException').slice();
|
|
769
|
+
process.removeAllListeners('uncaughtException');
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
afterEach(() => {
|
|
773
|
+
prevUncaughtExceptionListeners.forEach((h) => process.on('uncaughtException', h));
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it('throw in close handler is delivered via handler-error event', channelTest((ch, cb) => {
|
|
777
|
+
const expectedErr = new Error('user close handler explodes');
|
|
778
|
+
ch.on('handler-error', (err, event) => {
|
|
779
|
+
assert.strictEqual(err, expectedErr);
|
|
780
|
+
assert.strictEqual(event, 'close');
|
|
781
|
+
cb();
|
|
782
|
+
});
|
|
783
|
+
// A server-initiated ChannelClose always fires 'error' before 'close' (line 291 in channel.js).
|
|
784
|
+
// Handle it so only the throw from the close handler reaches handler-error.
|
|
785
|
+
ch.once('error', (err) => { assert.match(err.message, /Channel closed by server/); });
|
|
786
|
+
ch.on('close', () => { throw expectedErr; });
|
|
787
|
+
open(ch);
|
|
788
|
+
}, (send, wait, cb, ch) => {
|
|
789
|
+
send(defs.ChannelClose, {
|
|
790
|
+
replyText: 'Forced close',
|
|
791
|
+
replyCode: defs.constants.CHANNEL_ERROR,
|
|
792
|
+
classId: 0,
|
|
793
|
+
methodId: 0,
|
|
794
|
+
}, ch);
|
|
795
|
+
wait(defs.ChannelCloseOk)().then(cb, cb);
|
|
796
|
+
}));
|
|
797
|
+
|
|
798
|
+
it('throw in error handler is delivered via handler-error event', channelTest((ch, cb) => {
|
|
799
|
+
const expectedErr = new Error('user error handler explodes');
|
|
800
|
+
ch.on('handler-error', (err, event) => {
|
|
801
|
+
assert.strictEqual(err, expectedErr);
|
|
802
|
+
assert.strictEqual(event, 'error');
|
|
803
|
+
cb();
|
|
804
|
+
});
|
|
805
|
+
ch.on('error', () => { throw expectedErr; });
|
|
806
|
+
open(ch);
|
|
807
|
+
}, (send, wait, cb, ch) => {
|
|
808
|
+
send(defs.ChannelClose, {
|
|
809
|
+
replyText: 'Forced close',
|
|
810
|
+
replyCode: defs.constants.CHANNEL_ERROR,
|
|
811
|
+
classId: 0,
|
|
812
|
+
methodId: 0,
|
|
813
|
+
}, ch);
|
|
814
|
+
wait(defs.ChannelCloseOk)().then(cb, cb);
|
|
815
|
+
}));
|
|
816
|
+
|
|
817
|
+
it('throw in drain handler is delivered via handler-error event', channelTest((ch, cb) => {
|
|
818
|
+
const expectedErr = new Error('user drain handler explodes');
|
|
819
|
+
ch.on('handler-error', (err, event) => {
|
|
820
|
+
assert.strictEqual(err, expectedErr);
|
|
821
|
+
assert.strictEqual(event, 'drain');
|
|
822
|
+
cb();
|
|
823
|
+
});
|
|
824
|
+
ch.on('drain', () => { throw expectedErr; });
|
|
825
|
+
open(ch).then(() => setImmediate(() => ch.onBufferDrain()));
|
|
826
|
+
}, (_send, _wait, cb) => { cb(); }));
|
|
827
|
+
|
|
828
|
+
it('throw in ack handler is delivered via handler-error event', channelTest((ch, cb) => {
|
|
829
|
+
const expectedErr = new Error('user ack handler explodes');
|
|
830
|
+
ch.on('handler-error', (err, event) => {
|
|
831
|
+
assert.strictEqual(err, expectedErr);
|
|
832
|
+
assert.strictEqual(event, 'ack');
|
|
833
|
+
cb();
|
|
834
|
+
});
|
|
835
|
+
ch.on('ack', () => { throw expectedErr; });
|
|
836
|
+
open(ch);
|
|
837
|
+
}, (send, _wait, cb, ch) => {
|
|
838
|
+
completes(() => {
|
|
839
|
+
send(defs.BasicAck, { deliveryTag: 1, multiple: false }, ch);
|
|
840
|
+
}, cb);
|
|
841
|
+
}));
|
|
842
|
+
|
|
843
|
+
it('throw in nack handler is delivered via handler-error event', channelTest((ch, cb) => {
|
|
844
|
+
const expectedErr = new Error('user nack handler explodes');
|
|
845
|
+
ch.on('handler-error', (err, event) => {
|
|
846
|
+
assert.strictEqual(err, expectedErr);
|
|
847
|
+
assert.strictEqual(event, 'nack');
|
|
848
|
+
cb();
|
|
849
|
+
});
|
|
850
|
+
ch.on('nack', () => { throw expectedErr; });
|
|
851
|
+
open(ch);
|
|
852
|
+
}, (send, _wait, cb, ch) => {
|
|
853
|
+
completes(() => {
|
|
854
|
+
send(defs.BasicNack, { deliveryTag: 1, multiple: false, requeue: false }, ch);
|
|
855
|
+
}, cb);
|
|
856
|
+
}));
|
|
857
|
+
|
|
858
|
+
it('throw in cancel handler is delivered via handler-error event', channelTest((ch, cb) => {
|
|
859
|
+
const expectedErr = new Error('user cancel handler explodes');
|
|
860
|
+
ch.on('handler-error', (err, event) => {
|
|
861
|
+
assert.strictEqual(err, expectedErr);
|
|
862
|
+
assert.strictEqual(event, 'cancel');
|
|
863
|
+
cb();
|
|
864
|
+
});
|
|
865
|
+
ch.on('cancel', () => { throw expectedErr; });
|
|
866
|
+
open(ch);
|
|
867
|
+
}, (send, _wait, cb, ch) => {
|
|
868
|
+
completes(() => {
|
|
869
|
+
send(defs.BasicCancel, { consumerTag: 'tag', nowait: false }, ch);
|
|
870
|
+
}, cb);
|
|
871
|
+
}));
|
|
872
|
+
|
|
873
|
+
it('throw in delivery handler is delivered via handler-error event', channelTest((ch, cb) => {
|
|
874
|
+
const expectedErr = new Error('user delivery handler explodes');
|
|
875
|
+
ch.on('handler-error', (err, event) => {
|
|
876
|
+
assert.strictEqual(err, expectedErr);
|
|
877
|
+
assert.strictEqual(event, 'delivery');
|
|
878
|
+
cb();
|
|
879
|
+
});
|
|
880
|
+
ch.on('delivery', () => { throw expectedErr; });
|
|
881
|
+
open(ch);
|
|
882
|
+
}, (send, _wait, cb, ch) => {
|
|
883
|
+
completes(() => {
|
|
884
|
+
send(defs.BasicDeliver, DELIVER_FIELDS, ch, Buffer.from('hello'));
|
|
885
|
+
}, cb);
|
|
886
|
+
}));
|
|
887
|
+
|
|
888
|
+
it('throw in return handler is delivered via handler-error event', channelTest((ch, cb) => {
|
|
889
|
+
const expectedErr = new Error('user return handler explodes');
|
|
890
|
+
ch.on('handler-error', (err, event) => {
|
|
891
|
+
assert.strictEqual(err, expectedErr);
|
|
892
|
+
assert.strictEqual(event, 'return');
|
|
893
|
+
cb();
|
|
894
|
+
});
|
|
895
|
+
ch.on('return', () => { throw expectedErr; });
|
|
896
|
+
open(ch);
|
|
897
|
+
}, (send, _wait, cb, ch) => {
|
|
898
|
+
completes(() => {
|
|
899
|
+
send(defs.BasicReturn, DELIVER_FIELDS, ch, Buffer.from('hello'));
|
|
900
|
+
}, cb);
|
|
901
|
+
}));
|
|
902
|
+
|
|
903
|
+
it('throw in handler-error handler becomes uncaught exception', channelTest((ch, cb) => {
|
|
904
|
+
const expectedErr = new Error('handler-error handler explodes');
|
|
905
|
+
process.once('uncaughtException', (err) => {
|
|
906
|
+
assert.strictEqual(err, expectedErr);
|
|
907
|
+
cb();
|
|
908
|
+
});
|
|
909
|
+
ch.on('handler-error', () => { throw expectedErr; });
|
|
910
|
+
ch.on('ack', () => { throw new Error('user ack handler explodes'); });
|
|
911
|
+
open(ch);
|
|
912
|
+
}, (send, _wait, cb, ch) => {
|
|
913
|
+
completes(() => {
|
|
914
|
+
send(defs.BasicAck, { deliveryTag: 1, multiple: false }, ch);
|
|
915
|
+
}, cb);
|
|
916
|
+
}));
|
|
917
|
+
});
|
|
612
918
|
});
|
package/test/channel_api.test.js
CHANGED
|
@@ -25,6 +25,15 @@ describe('updateSecret', () => {
|
|
|
25
25
|
.finally(() => c.close())
|
|
26
26
|
);
|
|
27
27
|
});
|
|
28
|
+
|
|
29
|
+
it('emits update-secret-ok event', () => {
|
|
30
|
+
return connect().then((c) =>
|
|
31
|
+
new Promise((resolve, reject) => {
|
|
32
|
+
c.on('update-secret-ok', resolve);
|
|
33
|
+
c.updateSecret(Buffer.from('new secret'), 'no reason').catch(reject);
|
|
34
|
+
}).finally(() => c.close())
|
|
35
|
+
);
|
|
36
|
+
});
|
|
28
37
|
});
|
|
29
38
|
|
|
30
39
|
describe('assert, check, delete', () => {
|
|
@@ -176,6 +185,48 @@ describe('sendMessage', () => {
|
|
|
176
185
|
});
|
|
177
186
|
});
|
|
178
187
|
|
|
188
|
+
describe('get', () => {
|
|
189
|
+
it('returns false when queue is empty', () => {
|
|
190
|
+
return withChannel((ch) =>
|
|
191
|
+
ch.assertQueue('test.get-empty', QUEUE_OPTS)
|
|
192
|
+
.then(() => ch.purgeQueue('test.get-empty'))
|
|
193
|
+
.then(() => ch.get('test.get-empty', { noAck: true }))
|
|
194
|
+
.then((m) => assert.strictEqual(false, m))
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('returns a message when queue has messages', () => {
|
|
199
|
+
const msg = randomString();
|
|
200
|
+
return withChannel((ch) =>
|
|
201
|
+
ch.assertQueue('test.get-message', QUEUE_OPTS)
|
|
202
|
+
.then(() => ch.purgeQueue('test.get-message'))
|
|
203
|
+
.then(() => {
|
|
204
|
+
ch.sendToQueue('test.get-message', Buffer.from(msg));
|
|
205
|
+
return waitForMessages('test.get-message');
|
|
206
|
+
})
|
|
207
|
+
.then(() => ch.get('test.get-message', { noAck: true }))
|
|
208
|
+
.then((m) => {
|
|
209
|
+
assert(m);
|
|
210
|
+
assert.equal(msg, m.content.toString());
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('rejects with a proper error when queue does not exist', () => {
|
|
216
|
+
return withChannel((ch) => {
|
|
217
|
+
ch.once('error', ignore);
|
|
218
|
+
return assert.rejects(() => ch.get('', {}), (err) => {
|
|
219
|
+
assert(err instanceof Error);
|
|
220
|
+
assert.match(err.message, /NOT_FOUND/);
|
|
221
|
+
assert.strictEqual(404, err.code);
|
|
222
|
+
assert.strictEqual(60, err.classId);
|
|
223
|
+
assert.strictEqual(70, err.methodId);
|
|
224
|
+
return true;
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
179
230
|
describe('binding, consuming', () => {
|
|
180
231
|
// bind, publish, get
|
|
181
232
|
it('route message', () => {
|
package/test/connection.test.js
CHANGED
|
@@ -300,6 +300,357 @@ describe('Connection', () => {
|
|
|
300
300
|
}));
|
|
301
301
|
});
|
|
302
302
|
|
|
303
|
+
describe('Event handler errors - without handler-event listener', () => {
|
|
304
|
+
let prevUncaughtExceptionListeners;
|
|
305
|
+
|
|
306
|
+
beforeEach(() => {
|
|
307
|
+
prevUncaughtExceptionListeners = process.rawListeners('uncaughtException').slice();
|
|
308
|
+
process.removeAllListeners('uncaughtException');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
afterEach(() => {
|
|
312
|
+
prevUncaughtExceptionListeners.forEach((h) => process.on('uncaughtException', h));
|
|
313
|
+
heartbeat.UNITS_TO_MS = 1000;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('throw in close handler from server-initiated close is swallowed without handler-error listener', connectionTest((c, cb) => {
|
|
317
|
+
c.on('close', () => { throw new Error('user handler explodes'); });
|
|
318
|
+
c.open(OPEN_OPTS);
|
|
319
|
+
cb();
|
|
320
|
+
}, (send, wait, cb) => {
|
|
321
|
+
handshake(send, wait)
|
|
322
|
+
.then(() => send(defs.ConnectionClose, {
|
|
323
|
+
replyText: 'Begone',
|
|
324
|
+
replyCode: defs.constants.CONNECTION_FORCED,
|
|
325
|
+
methodId: 0,
|
|
326
|
+
classId: 0,
|
|
327
|
+
}))
|
|
328
|
+
.then(wait(defs.ConnectionCloseOk))
|
|
329
|
+
.then(cb, cb);
|
|
330
|
+
}));
|
|
331
|
+
|
|
332
|
+
it('throw in close handler from client-initiated close is swallowed without handler-error listener', connectionTest((c, cb) => {
|
|
333
|
+
c.open(OPEN_OPTS, (err) => {
|
|
334
|
+
assert.ifError(err);
|
|
335
|
+
c.on('close', () => { throw new Error('user handler explodes on client close'); });
|
|
336
|
+
c.close();
|
|
337
|
+
cb();
|
|
338
|
+
});
|
|
339
|
+
}, (send, wait, cb) => {
|
|
340
|
+
handshake(send, wait)
|
|
341
|
+
.then(wait(defs.ConnectionClose))
|
|
342
|
+
.then(() => send(defs.ConnectionCloseOk, {}))
|
|
343
|
+
.then(cb, cb);
|
|
344
|
+
}));
|
|
345
|
+
|
|
346
|
+
it('throw in error handler becomes uncaught exception', connectionTest((c, cb) => {
|
|
347
|
+
const expectedErr = new Error('user error handler explodes');
|
|
348
|
+
process.once('uncaughtException', (err) => {
|
|
349
|
+
assert.strictEqual(err, expectedErr);
|
|
350
|
+
cb();
|
|
351
|
+
});
|
|
352
|
+
c.on('error', () => { throw expectedErr; });
|
|
353
|
+
c.open(OPEN_OPTS);
|
|
354
|
+
}, (send, wait, cb) => {
|
|
355
|
+
handshake(send, wait)
|
|
356
|
+
.then(() => send(defs.ConnectionClose, {
|
|
357
|
+
replyText: 'Begone',
|
|
358
|
+
replyCode: defs.constants.INTERNAL_ERROR,
|
|
359
|
+
methodId: 0,
|
|
360
|
+
classId: 0,
|
|
361
|
+
}))
|
|
362
|
+
.then(wait(defs.ConnectionCloseOk))
|
|
363
|
+
.then(cb, cb);
|
|
364
|
+
}));
|
|
365
|
+
|
|
366
|
+
it('throw in blocked handler becomes uncaught exception', connectionTest((c, cb) => {
|
|
367
|
+
const expectedErr = new Error('user blocked handler explodes');
|
|
368
|
+
process.once('uncaughtException', (err) => {
|
|
369
|
+
assert.strictEqual(err, expectedErr);
|
|
370
|
+
cb();
|
|
371
|
+
});
|
|
372
|
+
c.on('blocked', () => { throw expectedErr; });
|
|
373
|
+
c.open(OPEN_OPTS);
|
|
374
|
+
}, (send, wait, cb) => {
|
|
375
|
+
handshake(send, wait)
|
|
376
|
+
.then(() => send(defs.ConnectionBlocked, { reason: 'memory' }, 0))
|
|
377
|
+
.then(cb, cb);
|
|
378
|
+
}));
|
|
379
|
+
|
|
380
|
+
it('throw in unblocked handler becomes uncaught exception', connectionTest((c, cb) => {
|
|
381
|
+
const expectedErr = new Error('user unblocked handler explodes');
|
|
382
|
+
process.once('uncaughtException', (err) => {
|
|
383
|
+
assert.strictEqual(err, expectedErr);
|
|
384
|
+
cb();
|
|
385
|
+
});
|
|
386
|
+
c.on('unblocked', () => { throw expectedErr; });
|
|
387
|
+
c.open(OPEN_OPTS);
|
|
388
|
+
}, (send, wait, cb) => {
|
|
389
|
+
handshake(send, wait)
|
|
390
|
+
.then(() => send(defs.ConnectionUnblocked, {}, 0))
|
|
391
|
+
.then(cb, cb);
|
|
392
|
+
}));
|
|
393
|
+
|
|
394
|
+
it('throw in update-secret-ok handler becomes uncaught exception', connectionTest((c, cb) => {
|
|
395
|
+
const expectedErr = new Error('user update-secret-ok handler explodes');
|
|
396
|
+
process.once('uncaughtException', (err) => {
|
|
397
|
+
assert.strictEqual(err, expectedErr);
|
|
398
|
+
cb();
|
|
399
|
+
});
|
|
400
|
+
c.open(OPEN_OPTS, (err) => {
|
|
401
|
+
assert.ifError(err);
|
|
402
|
+
c.on('update-secret-ok', () => { throw expectedErr; });
|
|
403
|
+
c._updateSecret(Buffer.from('new secret'), 'reason', () => {});
|
|
404
|
+
});
|
|
405
|
+
}, (send, wait, cb) => {
|
|
406
|
+
handshake(send, wait)
|
|
407
|
+
.then(wait(defs.ConnectionUpdateSecret))
|
|
408
|
+
.then(() => send(defs.ConnectionUpdateSecretOk, {}, 0))
|
|
409
|
+
.then(cb, cb);
|
|
410
|
+
}));
|
|
411
|
+
|
|
412
|
+
it('throw in error handler from closeWithError becomes uncaught exception', connectionTest((c, cb) => {
|
|
413
|
+
const expectedErr = new Error('user error handler explodes on closeWithError');
|
|
414
|
+
process.once('uncaughtException', (err) => {
|
|
415
|
+
assert.strictEqual(err, expectedErr);
|
|
416
|
+
cb();
|
|
417
|
+
});
|
|
418
|
+
c.once('error', (err) => {
|
|
419
|
+
assert.match(err.message, /Unexpected frame on channel 0/);
|
|
420
|
+
throw expectedErr;
|
|
421
|
+
});
|
|
422
|
+
c.open(OPEN_OPTS);
|
|
423
|
+
}, (send, wait, cb) => {
|
|
424
|
+
handshake(send, wait)
|
|
425
|
+
.then(() => send(defs.ChannelOpenOk, { channelId: Buffer.from('') }, 0))
|
|
426
|
+
.then(cb, cb);
|
|
427
|
+
}));
|
|
428
|
+
|
|
429
|
+
it('throw in error handler from onSocketError', connectionTest((c, cb) => {
|
|
430
|
+
const expectedErr = new Error('user error handler explodes on socket error');
|
|
431
|
+
process.once('uncaughtException', (err) => {
|
|
432
|
+
assert.strictEqual(err, expectedErr);
|
|
433
|
+
cb();
|
|
434
|
+
});
|
|
435
|
+
c.once('error', (err) => {
|
|
436
|
+
assert.match(err.message, /Unexpected close/);
|
|
437
|
+
throw expectedErr;
|
|
438
|
+
});
|
|
439
|
+
c.open(OPEN_OPTS, (err) => {
|
|
440
|
+
assert.ifError(err);
|
|
441
|
+
c.sendHeartbeat();
|
|
442
|
+
});
|
|
443
|
+
}, (send, wait, cb, socket) => {
|
|
444
|
+
handshake(send, wait)
|
|
445
|
+
.then(wait())
|
|
446
|
+
.then(() => socket.end())
|
|
447
|
+
.then(cb, cb);
|
|
448
|
+
}));
|
|
449
|
+
|
|
450
|
+
it('throw in error handler from heartbeat timeout', connectionTest((c, cb) => {
|
|
451
|
+
heartbeat.UNITS_TO_MS = 20;
|
|
452
|
+
const expectedErr = new Error('user error handler explodes on heartbeat timeout');
|
|
453
|
+
const opts = Object.create(OPEN_OPTS);
|
|
454
|
+
opts.heartbeat = 1;
|
|
455
|
+
process.once('uncaughtException', (err) => {
|
|
456
|
+
assert.strictEqual(err, expectedErr);
|
|
457
|
+
c.heartbeater.clear();
|
|
458
|
+
cb();
|
|
459
|
+
});
|
|
460
|
+
c.on('error', (err) => {
|
|
461
|
+
assert.match(err.message, /Heartbeat timeout/);
|
|
462
|
+
throw expectedErr;
|
|
463
|
+
});
|
|
464
|
+
c.open(opts);
|
|
465
|
+
}, (send, wait, cb) => {
|
|
466
|
+
handshake(send, wait)
|
|
467
|
+
.then(cb, cb);
|
|
468
|
+
// conspicuously not sending anything ...
|
|
469
|
+
}));
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
describe('Event handler errors - with handler-error listener', () => {
|
|
473
|
+
let prevUncaughtExceptionListeners;
|
|
474
|
+
|
|
475
|
+
beforeEach(() => {
|
|
476
|
+
prevUncaughtExceptionListeners = process.rawListeners('uncaughtException').slice();
|
|
477
|
+
process.removeAllListeners('uncaughtException');
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
afterEach(() => {
|
|
481
|
+
prevUncaughtExceptionListeners.forEach((h) => process.on('uncaughtException', h));
|
|
482
|
+
heartbeat.UNITS_TO_MS = 1000;
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('throw in close handler is delivered via handler-error event', connectionTest((c, cb) => {
|
|
486
|
+
const expectedErr = new Error('user close handler explodes');
|
|
487
|
+
c.on('handler-error', (err, event) => {
|
|
488
|
+
assert.strictEqual(err, expectedErr);
|
|
489
|
+
assert.strictEqual(event, 'close');
|
|
490
|
+
cb();
|
|
491
|
+
});
|
|
492
|
+
c.on('close', () => { throw expectedErr; });
|
|
493
|
+
c.open(OPEN_OPTS);
|
|
494
|
+
}, (send, wait, cb) => {
|
|
495
|
+
handshake(send, wait)
|
|
496
|
+
.then(() => send(defs.ConnectionClose, {
|
|
497
|
+
replyText: 'Begone',
|
|
498
|
+
replyCode: defs.constants.CONNECTION_FORCED,
|
|
499
|
+
methodId: 0,
|
|
500
|
+
classId: 0,
|
|
501
|
+
}))
|
|
502
|
+
.then(wait(defs.ConnectionCloseOk))
|
|
503
|
+
.then(cb, cb);
|
|
504
|
+
}));
|
|
505
|
+
|
|
506
|
+
it('throw in error handler is delivered via handler-error event', connectionTest((c, cb) => {
|
|
507
|
+
const expectedErr = new Error('user error handler explodes');
|
|
508
|
+
c.on('handler-error', (err, event) => {
|
|
509
|
+
assert.strictEqual(err, expectedErr);
|
|
510
|
+
assert.strictEqual(event, 'error');
|
|
511
|
+
cb();
|
|
512
|
+
});
|
|
513
|
+
c.on('error', () => { throw expectedErr; });
|
|
514
|
+
c.open(OPEN_OPTS);
|
|
515
|
+
}, (send, wait, cb) => {
|
|
516
|
+
handshake(send, wait)
|
|
517
|
+
.then(() => send(defs.ConnectionClose, {
|
|
518
|
+
replyText: 'Begone',
|
|
519
|
+
replyCode: defs.constants.INTERNAL_ERROR,
|
|
520
|
+
methodId: 0,
|
|
521
|
+
classId: 0,
|
|
522
|
+
}))
|
|
523
|
+
.then(wait(defs.ConnectionCloseOk))
|
|
524
|
+
.then(cb, cb);
|
|
525
|
+
}));
|
|
526
|
+
|
|
527
|
+
it('throw in blocked handler is delivered via handler-error event', connectionTest((c, cb) => {
|
|
528
|
+
const expectedErr = new Error('user blocked handler explodes');
|
|
529
|
+
c.on('handler-error', (err, event) => {
|
|
530
|
+
assert.strictEqual(err, expectedErr);
|
|
531
|
+
assert.strictEqual(event, 'blocked');
|
|
532
|
+
cb();
|
|
533
|
+
});
|
|
534
|
+
c.on('blocked', () => { throw expectedErr; });
|
|
535
|
+
c.open(OPEN_OPTS);
|
|
536
|
+
}, (send, wait, cb) => {
|
|
537
|
+
handshake(send, wait)
|
|
538
|
+
.then(() => send(defs.ConnectionBlocked, { reason: 'memory' }, 0))
|
|
539
|
+
.then(cb, cb);
|
|
540
|
+
}));
|
|
541
|
+
|
|
542
|
+
it('throw in unblocked handler is delivered via handler-error event', connectionTest((c, cb) => {
|
|
543
|
+
const expectedErr = new Error('user unblocked handler explodes');
|
|
544
|
+
c.on('handler-error', (err, event) => {
|
|
545
|
+
assert.strictEqual(err, expectedErr);
|
|
546
|
+
assert.strictEqual(event, 'unblocked');
|
|
547
|
+
cb();
|
|
548
|
+
});
|
|
549
|
+
c.on('unblocked', () => { throw expectedErr; });
|
|
550
|
+
c.open(OPEN_OPTS);
|
|
551
|
+
}, (send, wait, cb) => {
|
|
552
|
+
handshake(send, wait)
|
|
553
|
+
.then(() => send(defs.ConnectionUnblocked, {}, 0))
|
|
554
|
+
.then(cb, cb);
|
|
555
|
+
}));
|
|
556
|
+
|
|
557
|
+
it('throw in update-secret-ok handler is delivered via handler-error event', connectionTest((c, cb) => {
|
|
558
|
+
const expectedErr = new Error('user update-secret-ok handler explodes');
|
|
559
|
+
c.on('handler-error', (err, event) => {
|
|
560
|
+
assert.strictEqual(err, expectedErr);
|
|
561
|
+
assert.strictEqual(event, 'update-secret-ok');
|
|
562
|
+
cb();
|
|
563
|
+
});
|
|
564
|
+
c.open(OPEN_OPTS, (err) => {
|
|
565
|
+
assert.ifError(err);
|
|
566
|
+
c.on('update-secret-ok', () => { throw expectedErr; });
|
|
567
|
+
c._updateSecret(Buffer.from('new secret'), 'reason', () => {});
|
|
568
|
+
});
|
|
569
|
+
}, (send, wait, cb) => {
|
|
570
|
+
handshake(send, wait)
|
|
571
|
+
.then(wait(defs.ConnectionUpdateSecret))
|
|
572
|
+
.then(() => send(defs.ConnectionUpdateSecretOk, {}, 0))
|
|
573
|
+
.then(cb, cb);
|
|
574
|
+
}));
|
|
575
|
+
|
|
576
|
+
it('throw in error handler from heartbeat timeout is delivered via handler-error event', connectionTest((c, cb) => {
|
|
577
|
+
heartbeat.UNITS_TO_MS = 20;
|
|
578
|
+
const expectedErr = new Error('user error handler explodes on heartbeat timeout');
|
|
579
|
+
const opts = Object.create(OPEN_OPTS);
|
|
580
|
+
opts.heartbeat = 1;
|
|
581
|
+
c.on('handler-error', (err, event) => {
|
|
582
|
+
assert.strictEqual(err, expectedErr);
|
|
583
|
+
assert.strictEqual(event, 'error');
|
|
584
|
+
c.heartbeater.clear();
|
|
585
|
+
cb();
|
|
586
|
+
});
|
|
587
|
+
c.on('error', (err) => {
|
|
588
|
+
assert.match(err.message, /Heartbeat timeout/);
|
|
589
|
+
throw expectedErr;
|
|
590
|
+
});
|
|
591
|
+
c.open(opts);
|
|
592
|
+
}, (send, wait, cb) => {
|
|
593
|
+
handshake(send, wait)
|
|
594
|
+
.then(cb, cb);
|
|
595
|
+
// conspicuously not sending anything ...
|
|
596
|
+
}));
|
|
597
|
+
|
|
598
|
+
it('throw in error handler from closeWithError is delivered via handler-error event', connectionTest((c, cb) => {
|
|
599
|
+
const expectedErr = new Error('user error handler explodes on closeWithError');
|
|
600
|
+
c.on('handler-error', (err, event) => {
|
|
601
|
+
assert.strictEqual(err, expectedErr);
|
|
602
|
+
assert.strictEqual(event, 'error');
|
|
603
|
+
cb();
|
|
604
|
+
});
|
|
605
|
+
c.once('error', (err) => {
|
|
606
|
+
assert.match(err.message, /Unexpected frame on channel 0/);
|
|
607
|
+
throw expectedErr;
|
|
608
|
+
});
|
|
609
|
+
c.open(OPEN_OPTS);
|
|
610
|
+
}, (send, wait, cb) => {
|
|
611
|
+
handshake(send, wait)
|
|
612
|
+
.then(() => send(defs.ChannelOpenOk, { channelId: Buffer.from('') }, 0))
|
|
613
|
+
.then(cb, cb);
|
|
614
|
+
}));
|
|
615
|
+
|
|
616
|
+
it('throw in error handler from onSocketError is delivered via handler-error event', connectionTest((c, cb) => {
|
|
617
|
+
const expectedErr = new Error('user error handler explodes on socket error');
|
|
618
|
+
c.on('handler-error', (err, event) => {
|
|
619
|
+
assert.strictEqual(err, expectedErr);
|
|
620
|
+
assert.strictEqual(event, 'error');
|
|
621
|
+
cb();
|
|
622
|
+
});
|
|
623
|
+
c.once('error', (err) => {
|
|
624
|
+
assert.match(err.message, /Unexpected close/);
|
|
625
|
+
throw expectedErr;
|
|
626
|
+
});
|
|
627
|
+
c.open(OPEN_OPTS, (err) => {
|
|
628
|
+
assert.ifError(err);
|
|
629
|
+
c.sendHeartbeat();
|
|
630
|
+
});
|
|
631
|
+
}, (send, wait, cb, socket) => {
|
|
632
|
+
handshake(send, wait)
|
|
633
|
+
.then(wait())
|
|
634
|
+
.then(() => socket.end())
|
|
635
|
+
.then(cb, cb);
|
|
636
|
+
}));
|
|
637
|
+
|
|
638
|
+
it('throw in handler-error handler becomes uncaught exception', connectionTest((c, cb) => {
|
|
639
|
+
const expectedErr = new Error('handler-error handler explodes');
|
|
640
|
+
process.once('uncaughtException', (err) => {
|
|
641
|
+
assert.strictEqual(err, expectedErr);
|
|
642
|
+
cb();
|
|
643
|
+
});
|
|
644
|
+
c.on('handler-error', () => { throw expectedErr; });
|
|
645
|
+
c.on('blocked', () => { throw new Error('user blocked handler explodes'); });
|
|
646
|
+
c.open(OPEN_OPTS);
|
|
647
|
+
}, (send, wait, cb) => {
|
|
648
|
+
handshake(send, wait)
|
|
649
|
+
.then(() => send(defs.ConnectionBlocked, { reason: 'memory' }, 0))
|
|
650
|
+
.then(cb, cb);
|
|
651
|
+
}));
|
|
652
|
+
});
|
|
653
|
+
|
|
303
654
|
describe('heartbeats', () => {
|
|
304
655
|
|
|
305
656
|
beforeEach(() => {
|