@onify/fake-amqplib 3.4.0 → 3.6.0
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/README.md +76 -44
- package/index.d.ts +70 -25
- package/index.js +94 -3
- package/main.cjs +96 -3
- package/package.json +22 -11
package/README.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
Mocked version of https://www.npmjs.com/package/amqplib.
|
|
6
6
|
|
|
7
|
+
<!-- toc -->
|
|
8
|
+
|
|
9
|
+
- [Fake api](#fake-api)
|
|
10
|
+
- [RabbitMQ versions](#rabbitmq-versions)
|
|
11
|
+
- [Mocking amqplib](#mocking-amqplib)
|
|
12
|
+
- [ESM](#esm)
|
|
13
|
+
- [Node 20+ — `node:test` `mock.module` (recommended)](#node-20-nodetest-mockmodule-recommended)
|
|
14
|
+
- [Alternative — Quibble](#alternative-quibble)
|
|
15
|
+
- [CommonJS](#commonjs)
|
|
16
|
+
|
|
17
|
+
<!-- /toc -->
|
|
18
|
+
|
|
7
19
|
## Fake api
|
|
8
20
|
|
|
9
21
|
- `async connect(amqpurl[, ...otherOptions, callback])`: wait for a fake connection or expect one in the callback
|
|
@@ -38,46 +50,62 @@ var fakeAmqp = require('@onify/fake-amqplib');
|
|
|
38
50
|
|
|
39
51
|
You might want to override `amqplib` with `@onify/fake-amqplib` in tests. This can be done in a number of ways.
|
|
40
52
|
|
|
41
|
-
###
|
|
53
|
+
### ESM
|
|
42
54
|
|
|
43
|
-
Example on how to mock amqplib when working with
|
|
55
|
+
Example on how to mock the `amqplib` import when working with modules.
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
const amqplib = require('amqplib');
|
|
47
|
-
const fakeAmqp = require('@onify/fake-amqplib');
|
|
57
|
+
#### Node 20+ — `node:test` `mock.module` (recommended)
|
|
48
58
|
|
|
49
|
-
|
|
59
|
+
Node's built-in test runner ships an experimental module-mocking API that needs no extra dependency. Set it up at the top of the test file (or in a setup file), then dynamically import `amqplib`.
|
|
60
|
+
|
|
61
|
+
_.mocharc.json_
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"recursive": true,
|
|
66
|
+
"require": ["chai/register-expect.js"],
|
|
67
|
+
"node-option": ["experimental-test-module-mocks", "no-warnings"]
|
|
68
|
+
}
|
|
50
69
|
```
|
|
51
70
|
|
|
52
|
-
|
|
71
|
+
The `experimental-test-module-mocks` flag enables `mock.module`; `no-warnings` silences the "experimental feature" notice. Drop it if you'd rather see the warning.
|
|
53
72
|
|
|
54
|
-
|
|
55
|
-
const mock = require('mock-require');
|
|
56
|
-
const fakeAmqp = require('@onify/fake-amqplib');
|
|
73
|
+
_test/amqplib-connection-test.js_
|
|
57
74
|
|
|
58
|
-
|
|
59
|
-
|
|
75
|
+
```javascript
|
|
76
|
+
import { mock } from 'node:test';
|
|
77
|
+
import { connect as fakeConnect, resetMock } from '@onify/fake-amqplib';
|
|
60
78
|
|
|
61
|
-
|
|
79
|
+
describe('connection', () => {
|
|
80
|
+
let connect;
|
|
81
|
+
let ctx;
|
|
62
82
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
before(async () => {
|
|
84
|
+
ctx = mock.module('amqplib', { namedExports: { connect: fakeConnect } });
|
|
85
|
+
({ connect } = await import('amqplib'));
|
|
86
|
+
});
|
|
66
87
|
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
after(() => {
|
|
89
|
+
ctx.restore();
|
|
90
|
+
resetMock();
|
|
91
|
+
});
|
|
69
92
|
|
|
70
|
-
|
|
93
|
+
it('connects to the fake', async () => {
|
|
94
|
+
const connection = await connect('amqp://host');
|
|
95
|
+
expect(connection.connection.serverProperties).to.have.property('product', 'RabbitMQ');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
```
|
|
71
99
|
|
|
72
|
-
|
|
100
|
+
If you also use `mocha --parallel` or run tests via `node --test`, the same setup works — `mock.module` is process-global, so register it before the first dynamic import in each test file.
|
|
73
101
|
|
|
74
|
-
|
|
102
|
+
#### Alternative — Quibble
|
|
75
103
|
|
|
76
|
-
|
|
104
|
+
[Quibble](https://www.npmjs.com/package/quibble) is useful if you're on a Node version older than 20, or prefer not to rely on an experimental flag.
|
|
77
105
|
|
|
78
106
|
_test/setup.js_
|
|
79
107
|
|
|
80
|
-
```
|
|
108
|
+
```js
|
|
81
109
|
import * as fakeAmqpLib from '@onify/fake-amqplib';
|
|
82
110
|
import { connect as fakeConnect } from '@onify/fake-amqplib';
|
|
83
111
|
import quibble from 'quibble';
|
|
@@ -88,7 +116,7 @@ import quibble from 'quibble';
|
|
|
88
116
|
})();
|
|
89
117
|
```
|
|
90
118
|
|
|
91
|
-
_.mocharc.json_ (
|
|
119
|
+
_.mocharc.json_ (the `loader=quibble` option is only needed on Node < 20)
|
|
92
120
|
|
|
93
121
|
```json
|
|
94
122
|
{
|
|
@@ -98,29 +126,33 @@ _.mocharc.json_ (true for node version < 20)
|
|
|
98
126
|
}
|
|
99
127
|
```
|
|
100
128
|
|
|
101
|
-
|
|
129
|
+
Then import `amqplib` normally in your tests; quibble rewires the import.
|
|
102
130
|
|
|
103
|
-
|
|
104
|
-
import assert from 'node:assert';
|
|
105
|
-
import { connect } from 'amqplib';
|
|
106
|
-
import { connect as connectCb } from 'amqplib';
|
|
131
|
+
### CommonJS
|
|
107
132
|
|
|
108
|
-
|
|
133
|
+
Example on how to mock amqplib when working with commonjs.
|
|
109
134
|
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
```js
|
|
136
|
+
const amqplib = require('amqplib');
|
|
137
|
+
const fakeAmqp = require('@onify/fake-amqplib');
|
|
112
138
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
assert.equal(connection.connection.serverProperties.version, '3.5.0');
|
|
116
|
-
});
|
|
139
|
+
amqplib.connect = fakeAmqp.connect;
|
|
140
|
+
```
|
|
117
141
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
or:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
const mock = require('mock-require');
|
|
146
|
+
const fakeAmqp = require('@onify/fake-amqplib');
|
|
147
|
+
|
|
148
|
+
mock('amqplib/callback_api', fakeAmqp);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
or just mock the entire amqplib with:
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
const mock = require('mock-require');
|
|
155
|
+
const fakeAmqp = require('@onify/fake-amqplib');
|
|
156
|
+
|
|
157
|
+
mock('amqplib', fakeAmqp);
|
|
126
158
|
```
|
package/index.d.ts
CHANGED
|
@@ -1,59 +1,104 @@
|
|
|
1
|
-
/// <reference types="amqplib" />
|
|
2
1
|
/// <reference types="node" />
|
|
3
2
|
|
|
4
3
|
declare module '@onify/fake-amqplib' {
|
|
5
|
-
import { Options,
|
|
4
|
+
import { Options, Channel, ChannelModel, Replies, RecoveryOptions, SocketOptions } from 'amqplib';
|
|
6
5
|
import { Broker } from 'smqp';
|
|
7
6
|
|
|
7
|
+
export { RecoveryOptions, SocketOptions };
|
|
8
|
+
|
|
8
9
|
export interface FakeAmqplibChannel extends Channel {
|
|
9
10
|
/** Channel name and identifier, for faking purposes */
|
|
10
11
|
_channelName: string;
|
|
11
12
|
_broker: Broker;
|
|
12
13
|
_version: number;
|
|
14
|
+
readonly _closed: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const FakeAmqplibChannel: {
|
|
13
18
|
new (broker: Broker, connection: FakeAmqplibConnection): FakeAmqplibChannel;
|
|
14
|
-
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export interface FakeAmqplibConfirmChannel extends FakeAmqplibChannel {
|
|
22
|
+
publish(
|
|
23
|
+
exchange: string,
|
|
24
|
+
routingKey: string,
|
|
25
|
+
content: Buffer,
|
|
26
|
+
options?: Options.Publish,
|
|
27
|
+
callback?: (err: Error | null, ok: Replies.Empty) => void
|
|
28
|
+
): boolean;
|
|
29
|
+
sendToQueue(
|
|
30
|
+
queue: string,
|
|
31
|
+
content: Buffer,
|
|
32
|
+
options?: Options.Publish,
|
|
33
|
+
callback?: (err: Error | null, ok: Replies.Empty) => void
|
|
34
|
+
): boolean;
|
|
35
|
+
waitForConfirms(callback?: (err: Error | null) => void): Promise<void>;
|
|
15
36
|
}
|
|
16
37
|
|
|
17
|
-
export
|
|
38
|
+
export const FakeAmqplibConfirmChannel: {
|
|
39
|
+
new (broker: Broker, connection: FakeAmqplibConnection): FakeAmqplibConfirmChannel;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export interface FakeAmqplibConnection extends ChannelModel {
|
|
18
43
|
_channels: FakeAmqplibChannel[];
|
|
19
44
|
_url: URL;
|
|
20
45
|
/** Connection identifier, for faking purposes */
|
|
21
46
|
_id: string;
|
|
22
47
|
_broker: Broker;
|
|
23
48
|
_version: number;
|
|
24
|
-
|
|
25
|
-
get _closed(): boolean;
|
|
49
|
+
readonly _closed: boolean;
|
|
26
50
|
}
|
|
27
51
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
keepAliveDelay?: number;
|
|
32
|
-
noDelay?: boolean;
|
|
33
|
-
port?: number;
|
|
34
|
-
serverName?: string;
|
|
35
|
-
timeout?: number;
|
|
36
|
-
[x: string]: any;
|
|
37
|
-
}
|
|
52
|
+
export const FakeAmqplibConnection: {
|
|
53
|
+
new (broker: Broker, version: number, amqpUrl: string, options?: SocketOptions): FakeAmqplibConnection;
|
|
54
|
+
};
|
|
38
55
|
|
|
39
|
-
type
|
|
56
|
+
export type ConnectCallback = (err: Error | null, connection: FakeAmqplibConnection) => void;
|
|
40
57
|
|
|
41
|
-
export
|
|
58
|
+
export interface FakeAmqplib {
|
|
42
59
|
version: number;
|
|
43
60
|
connections: FakeAmqplibConnection[];
|
|
44
|
-
constructor(version?: number);
|
|
45
61
|
connect(url: string | Options.Connect, socketOptions?: SocketOptions): Promise<FakeAmqplibConnection>;
|
|
46
|
-
connect(url: string | Options.Connect, socketOptions: SocketOptions, callback:
|
|
47
|
-
connect(url: string | Options.Connect, callback:
|
|
62
|
+
connect(url: string | Options.Connect, socketOptions: SocketOptions, callback: ConnectCallback): void;
|
|
63
|
+
connect(url: string | Options.Connect, callback: ConnectCallback): void;
|
|
48
64
|
connectSync(url: string | Options.Connect, socketOptions?: SocketOptions): FakeAmqplibConnection;
|
|
65
|
+
connectWithRecoveryPromise(
|
|
66
|
+
url: string | Options.Connect,
|
|
67
|
+
recoveryOptions?: RecoveryOptions,
|
|
68
|
+
socketOptions?: SocketOptions
|
|
69
|
+
): Promise<FakeAmqplibConnection>;
|
|
70
|
+
connectWithRecoveryCallback(
|
|
71
|
+
url: string | Options.Connect,
|
|
72
|
+
recoveryOptions: RecoveryOptions,
|
|
73
|
+
socketOptions: SocketOptions,
|
|
74
|
+
callback: ConnectCallback
|
|
75
|
+
): void;
|
|
49
76
|
resetMock(): void;
|
|
50
|
-
setVersion(minorVersion: number): void;
|
|
77
|
+
setVersion(minorVersion: number | string): void;
|
|
51
78
|
}
|
|
52
79
|
|
|
80
|
+
export const FakeAmqplib: {
|
|
81
|
+
new (minorVersion?: number | string): FakeAmqplib;
|
|
82
|
+
(minorVersion?: number | string): FakeAmqplib;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const connections: FakeAmqplibConnection[];
|
|
86
|
+
|
|
53
87
|
export function connect(url: string | Options.Connect, socketOptions?: SocketOptions): Promise<FakeAmqplibConnection>;
|
|
54
|
-
export function connect(url: string | Options.Connect, socketOptions: SocketOptions, callback:
|
|
55
|
-
export function connect(url: string | Options.Connect, callback:
|
|
88
|
+
export function connect(url: string | Options.Connect, socketOptions: SocketOptions, callback: ConnectCallback): void;
|
|
89
|
+
export function connect(url: string | Options.Connect, callback: ConnectCallback): void;
|
|
56
90
|
export function connectSync(url: string | Options.Connect, socketOptions?: SocketOptions): FakeAmqplibConnection;
|
|
91
|
+
export function connectWithRecoveryPromise(
|
|
92
|
+
url: string | Options.Connect,
|
|
93
|
+
recoveryOptions?: RecoveryOptions,
|
|
94
|
+
socketOptions?: SocketOptions
|
|
95
|
+
): Promise<FakeAmqplibConnection>;
|
|
96
|
+
export function connectWithRecoveryCallback(
|
|
97
|
+
url: string | Options.Connect,
|
|
98
|
+
recoveryOptions: RecoveryOptions,
|
|
99
|
+
socketOptions: SocketOptions,
|
|
100
|
+
callback: ConnectCallback
|
|
101
|
+
): void;
|
|
57
102
|
export function resetMock(): void;
|
|
58
|
-
export function setVersion(minorVersion: number): void;
|
|
103
|
+
export function setVersion(minorVersion: number | string): void;
|
|
59
104
|
}
|
package/index.js
CHANGED
|
@@ -7,6 +7,9 @@ const kClosed = Symbol.for('closed');
|
|
|
7
7
|
const kDeliveryTag = Symbol.for('channel delivery tag');
|
|
8
8
|
const kPrefetch = Symbol.for('prefetch');
|
|
9
9
|
const kChannelPrefetch = Symbol.for('channel prefetch');
|
|
10
|
+
const kPendingConfirms = Symbol.for('pending confirms');
|
|
11
|
+
|
|
12
|
+
const CHANNEL_CLOSED_ERROR = 'Channel is closed';
|
|
10
13
|
|
|
11
14
|
class AmqplibBroker extends Broker {
|
|
12
15
|
constructor(...args) {
|
|
@@ -429,6 +432,21 @@ export class FakeAmqplibChannel extends EventEmitter {
|
|
|
429
432
|
brokerMessage.reject(requeue);
|
|
430
433
|
}
|
|
431
434
|
}
|
|
435
|
+
recover(...args) {
|
|
436
|
+
const channelQ = this._channelQueue;
|
|
437
|
+
return this._callBroker(recoverChannel, ...args);
|
|
438
|
+
|
|
439
|
+
function recoverChannel() {
|
|
440
|
+
const snapshot = [];
|
|
441
|
+
let msg;
|
|
442
|
+
while ((msg = channelQ.get())) snapshot.push(msg);
|
|
443
|
+
for (const m of snapshot) {
|
|
444
|
+
m.content[kSmqp].reject(true);
|
|
445
|
+
m.reject(false);
|
|
446
|
+
}
|
|
447
|
+
return {};
|
|
448
|
+
}
|
|
449
|
+
}
|
|
432
450
|
prefetch(val, isChannelPrefetch) {
|
|
433
451
|
if (this.connection._version < 3.3) {
|
|
434
452
|
if (isChannelPrefetch !== undefined) {
|
|
@@ -449,7 +467,7 @@ export class FakeAmqplibChannel extends EventEmitter {
|
|
|
449
467
|
else poppedCb = null;
|
|
450
468
|
|
|
451
469
|
if (this.connection._closed) throw new FakeAmqpError('Connection is closed', 504);
|
|
452
|
-
if (this[kClosed]) throw new Error(
|
|
470
|
+
if (this[kClosed]) throw new Error(CHANNEL_CLOSED_ERROR);
|
|
453
471
|
|
|
454
472
|
return new Promise((resolve, reject) => {
|
|
455
473
|
try {
|
|
@@ -509,19 +527,25 @@ export class FakeAmqplibChannel extends EventEmitter {
|
|
|
509
527
|
}
|
|
510
528
|
|
|
511
529
|
export class FakeAmqplibConfirmChannel extends FakeAmqplibChannel {
|
|
530
|
+
constructor(broker, connection) {
|
|
531
|
+
super(broker, connection);
|
|
532
|
+
this[kPendingConfirms] = new Set();
|
|
533
|
+
}
|
|
512
534
|
publish(exchange, routingKey, content, options, callback) {
|
|
513
535
|
if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
|
|
514
536
|
if (exchange === '') return this.sendToQueue(routingKey, content, options, callback);
|
|
537
|
+
if (this[kClosed]) throw new Error(CHANNEL_CLOSED_ERROR);
|
|
515
538
|
|
|
516
539
|
const args = [this._broker.publish, exchange, routingKey, content];
|
|
517
540
|
|
|
518
|
-
args.push(...addConfirmCallback(this._broker, options, callback));
|
|
541
|
+
args.push(...addConfirmCallback(this._broker, options, this._trackConfirm(callback)));
|
|
519
542
|
|
|
520
543
|
this.checkExchange(exchange)
|
|
521
544
|
.then(() => {
|
|
522
545
|
return this._callBroker(...args);
|
|
523
546
|
})
|
|
524
547
|
.catch((err) => {
|
|
548
|
+
if (err.message === CHANNEL_CLOSED_ERROR) return;
|
|
525
549
|
this.emit('error', err);
|
|
526
550
|
});
|
|
527
551
|
|
|
@@ -529,21 +553,61 @@ export class FakeAmqplibConfirmChannel extends FakeAmqplibChannel {
|
|
|
529
553
|
}
|
|
530
554
|
sendToQueue(queue, content, options, callback) {
|
|
531
555
|
if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
|
|
556
|
+
if (this[kClosed]) throw new Error(CHANNEL_CLOSED_ERROR);
|
|
532
557
|
|
|
533
558
|
const args = [this._broker.sendToQueue, queue, content];
|
|
534
559
|
|
|
535
|
-
args.push(...addConfirmCallback(this._broker, options, callback));
|
|
560
|
+
args.push(...addConfirmCallback(this._broker, options, this._trackConfirm(callback)));
|
|
536
561
|
|
|
537
562
|
this.checkQueue(queue)
|
|
538
563
|
.then(() => {
|
|
539
564
|
return this._callBroker(...args);
|
|
540
565
|
})
|
|
541
566
|
.catch((err) => {
|
|
567
|
+
if (err.message === CHANNEL_CLOSED_ERROR) return;
|
|
542
568
|
this.emit('error', err);
|
|
543
569
|
});
|
|
544
570
|
|
|
545
571
|
return true;
|
|
546
572
|
}
|
|
573
|
+
_trackConfirm(userCallback) {
|
|
574
|
+
let resolveSettled;
|
|
575
|
+
const settled = new Promise((resolve) => {
|
|
576
|
+
resolveSettled = resolve;
|
|
577
|
+
});
|
|
578
|
+
const entry = { settled, resolveSettled, userCallback };
|
|
579
|
+
this[kPendingConfirms].add(entry);
|
|
580
|
+
|
|
581
|
+
return (err, ok) => {
|
|
582
|
+
this[kPendingConfirms].delete(entry);
|
|
583
|
+
resolveSettled(err || null);
|
|
584
|
+
if (typeof userCallback === 'function') userCallback(err, ok);
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
waitForConfirms(callback) {
|
|
588
|
+
const snapshot = [...this[kPendingConfirms]].map((entry) => entry.settled);
|
|
589
|
+
const promise = Promise.all(snapshot).then((errs) => {
|
|
590
|
+
const firstErr = errs.find((e) => e !== null);
|
|
591
|
+
if (firstErr) throw firstErr;
|
|
592
|
+
});
|
|
593
|
+
if (typeof callback === 'function') {
|
|
594
|
+
promise.then(
|
|
595
|
+
() => callback(null),
|
|
596
|
+
(err) => callback(err)
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
return promise;
|
|
600
|
+
}
|
|
601
|
+
_teardown() {
|
|
602
|
+
super._teardown();
|
|
603
|
+
if (!this[kPendingConfirms] || this[kPendingConfirms].size === 0) return;
|
|
604
|
+
const err = new Error(CHANNEL_CLOSED_ERROR);
|
|
605
|
+
for (const entry of [...this[kPendingConfirms]]) {
|
|
606
|
+
this[kPendingConfirms].delete(entry);
|
|
607
|
+
entry.resolveSettled(err);
|
|
608
|
+
if (typeof entry.userCallback === 'function') entry.userCallback(err);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
547
611
|
}
|
|
548
612
|
|
|
549
613
|
export class FakeAmqplibConnection extends EventEmitter {
|
|
@@ -586,6 +650,10 @@ export class FakeAmqplibConnection extends EventEmitter {
|
|
|
586
650
|
this._channels.push(channel);
|
|
587
651
|
return resolveOrCallback(args.slice(-1)[0], null, channel);
|
|
588
652
|
}
|
|
653
|
+
updateSecret(...args) {
|
|
654
|
+
process.nextTick(() => this.emit('update-secret-ok'));
|
|
655
|
+
return resolveOrCallback(args.slice(-1)[0]);
|
|
656
|
+
}
|
|
589
657
|
close(...args) {
|
|
590
658
|
if (this[kClosed]) return resolveOrCallback(args.slice(-1)[0]);
|
|
591
659
|
this[kClosed] = true;
|
|
@@ -608,6 +676,8 @@ export function FakeAmqplib(minorVersion = '3.5') {
|
|
|
608
676
|
|
|
609
677
|
this.connect = this.connect.bind(this);
|
|
610
678
|
this.connectSync = this.connectSync.bind(this);
|
|
679
|
+
this.connectWithRecoveryPromise = this.connectWithRecoveryPromise.bind(this);
|
|
680
|
+
this.connectWithRecoveryCallback = this.connectWithRecoveryCallback.bind(this);
|
|
611
681
|
this.resetMock = this.resetMock.bind(this);
|
|
612
682
|
this.setVersion = this.setVersion.bind(this);
|
|
613
683
|
}
|
|
@@ -617,6 +687,19 @@ FakeAmqplib.prototype.connect = function fakeConnect(amqpUrl, ...args) {
|
|
|
617
687
|
return resolveOrCallback(args.slice(-1)[0], null, connection);
|
|
618
688
|
};
|
|
619
689
|
|
|
690
|
+
FakeAmqplib.prototype.connectWithRecoveryPromise = function fakeConnectWithRecoveryPromise(amqpUrl, recoveryOptions, socketOptions) {
|
|
691
|
+
return this.connect(amqpUrl, socketOptions);
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
FakeAmqplib.prototype.connectWithRecoveryCallback = function fakeConnectWithRecoveryCallback(
|
|
695
|
+
amqpUrl,
|
|
696
|
+
recoveryOptions,
|
|
697
|
+
socketOptions,
|
|
698
|
+
callback
|
|
699
|
+
) {
|
|
700
|
+
return this.connect(amqpUrl, socketOptions, callback);
|
|
701
|
+
};
|
|
702
|
+
|
|
620
703
|
FakeAmqplib.prototype.connectSync = function fakeConnectSync(amqpUrl, ...args) {
|
|
621
704
|
const { _broker } = this.connections.find((conn) => compareConnectionString(conn._url, amqpUrl)) || {};
|
|
622
705
|
const broker = _broker || new AmqplibBroker(this);
|
|
@@ -762,6 +845,14 @@ export function connectSync(amqpUrl, ...args) {
|
|
|
762
845
|
return defaultFake.connectSync(amqpUrl, ...args);
|
|
763
846
|
}
|
|
764
847
|
|
|
848
|
+
export function connectWithRecoveryPromise(amqpUrl, recoveryOptions, socketOptions) {
|
|
849
|
+
return defaultFake.connectWithRecoveryPromise(amqpUrl, recoveryOptions, socketOptions);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
export function connectWithRecoveryCallback(amqpUrl, recoveryOptions, socketOptions, callback) {
|
|
853
|
+
return defaultFake.connectWithRecoveryCallback(amqpUrl, recoveryOptions, socketOptions, callback);
|
|
854
|
+
}
|
|
855
|
+
|
|
765
856
|
export function resetMock() {
|
|
766
857
|
return defaultFake.resetMock();
|
|
767
858
|
}
|
package/main.cjs
CHANGED
|
@@ -9,6 +9,9 @@ const kClosed = Symbol.for('closed');
|
|
|
9
9
|
const kDeliveryTag = Symbol.for('channel delivery tag');
|
|
10
10
|
const kPrefetch = Symbol.for('prefetch');
|
|
11
11
|
const kChannelPrefetch = Symbol.for('channel prefetch');
|
|
12
|
+
const kPendingConfirms = Symbol.for('pending confirms');
|
|
13
|
+
|
|
14
|
+
const CHANNEL_CLOSED_ERROR = 'Channel is closed';
|
|
12
15
|
|
|
13
16
|
class AmqplibBroker extends smqp.Broker {
|
|
14
17
|
constructor(...args) {
|
|
@@ -431,6 +434,21 @@ class FakeAmqplibChannel extends events.EventEmitter {
|
|
|
431
434
|
brokerMessage.reject(requeue);
|
|
432
435
|
}
|
|
433
436
|
}
|
|
437
|
+
recover(...args) {
|
|
438
|
+
const channelQ = this._channelQueue;
|
|
439
|
+
return this._callBroker(recoverChannel, ...args);
|
|
440
|
+
|
|
441
|
+
function recoverChannel() {
|
|
442
|
+
const snapshot = [];
|
|
443
|
+
let msg;
|
|
444
|
+
while ((msg = channelQ.get())) snapshot.push(msg);
|
|
445
|
+
for (const m of snapshot) {
|
|
446
|
+
m.content[kSmqp].reject(true);
|
|
447
|
+
m.reject(false);
|
|
448
|
+
}
|
|
449
|
+
return {};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
434
452
|
prefetch(val, isChannelPrefetch) {
|
|
435
453
|
if (this.connection._version < 3.3) {
|
|
436
454
|
if (isChannelPrefetch !== undefined) {
|
|
@@ -451,7 +469,7 @@ class FakeAmqplibChannel extends events.EventEmitter {
|
|
|
451
469
|
else poppedCb = null;
|
|
452
470
|
|
|
453
471
|
if (this.connection._closed) throw new FakeAmqpError('Connection is closed', 504);
|
|
454
|
-
if (this[kClosed]) throw new Error(
|
|
472
|
+
if (this[kClosed]) throw new Error(CHANNEL_CLOSED_ERROR);
|
|
455
473
|
|
|
456
474
|
return new Promise((resolve, reject) => {
|
|
457
475
|
try {
|
|
@@ -511,19 +529,25 @@ class FakeAmqplibChannel extends events.EventEmitter {
|
|
|
511
529
|
}
|
|
512
530
|
|
|
513
531
|
class FakeAmqplibConfirmChannel extends FakeAmqplibChannel {
|
|
532
|
+
constructor(broker, connection) {
|
|
533
|
+
super(broker, connection);
|
|
534
|
+
this[kPendingConfirms] = new Set();
|
|
535
|
+
}
|
|
514
536
|
publish(exchange, routingKey, content, options, callback) {
|
|
515
537
|
if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
|
|
516
538
|
if (exchange === '') return this.sendToQueue(routingKey, content, options, callback);
|
|
539
|
+
if (this[kClosed]) throw new Error(CHANNEL_CLOSED_ERROR);
|
|
517
540
|
|
|
518
541
|
const args = [this._broker.publish, exchange, routingKey, content];
|
|
519
542
|
|
|
520
|
-
args.push(...addConfirmCallback(this._broker, options, callback));
|
|
543
|
+
args.push(...addConfirmCallback(this._broker, options, this._trackConfirm(callback)));
|
|
521
544
|
|
|
522
545
|
this.checkExchange(exchange)
|
|
523
546
|
.then(() => {
|
|
524
547
|
return this._callBroker(...args);
|
|
525
548
|
})
|
|
526
549
|
.catch((err) => {
|
|
550
|
+
if (err.message === CHANNEL_CLOSED_ERROR) return;
|
|
527
551
|
this.emit('error', err);
|
|
528
552
|
});
|
|
529
553
|
|
|
@@ -531,21 +555,61 @@ class FakeAmqplibConfirmChannel extends FakeAmqplibChannel {
|
|
|
531
555
|
}
|
|
532
556
|
sendToQueue(queue, content, options, callback) {
|
|
533
557
|
if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
|
|
558
|
+
if (this[kClosed]) throw new Error(CHANNEL_CLOSED_ERROR);
|
|
534
559
|
|
|
535
560
|
const args = [this._broker.sendToQueue, queue, content];
|
|
536
561
|
|
|
537
|
-
args.push(...addConfirmCallback(this._broker, options, callback));
|
|
562
|
+
args.push(...addConfirmCallback(this._broker, options, this._trackConfirm(callback)));
|
|
538
563
|
|
|
539
564
|
this.checkQueue(queue)
|
|
540
565
|
.then(() => {
|
|
541
566
|
return this._callBroker(...args);
|
|
542
567
|
})
|
|
543
568
|
.catch((err) => {
|
|
569
|
+
if (err.message === CHANNEL_CLOSED_ERROR) return;
|
|
544
570
|
this.emit('error', err);
|
|
545
571
|
});
|
|
546
572
|
|
|
547
573
|
return true;
|
|
548
574
|
}
|
|
575
|
+
_trackConfirm(userCallback) {
|
|
576
|
+
let resolveSettled;
|
|
577
|
+
const settled = new Promise((resolve) => {
|
|
578
|
+
resolveSettled = resolve;
|
|
579
|
+
});
|
|
580
|
+
const entry = { settled, resolveSettled, userCallback };
|
|
581
|
+
this[kPendingConfirms].add(entry);
|
|
582
|
+
|
|
583
|
+
return (err, ok) => {
|
|
584
|
+
this[kPendingConfirms].delete(entry);
|
|
585
|
+
resolveSettled(err || null);
|
|
586
|
+
if (typeof userCallback === 'function') userCallback(err, ok);
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
waitForConfirms(callback) {
|
|
590
|
+
const snapshot = [...this[kPendingConfirms]].map((entry) => entry.settled);
|
|
591
|
+
const promise = Promise.all(snapshot).then((errs) => {
|
|
592
|
+
const firstErr = errs.find((e) => e !== null);
|
|
593
|
+
if (firstErr) throw firstErr;
|
|
594
|
+
});
|
|
595
|
+
if (typeof callback === 'function') {
|
|
596
|
+
promise.then(
|
|
597
|
+
() => callback(null),
|
|
598
|
+
(err) => callback(err)
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return promise;
|
|
602
|
+
}
|
|
603
|
+
_teardown() {
|
|
604
|
+
super._teardown();
|
|
605
|
+
if (!this[kPendingConfirms] || this[kPendingConfirms].size === 0) return;
|
|
606
|
+
const err = new Error(CHANNEL_CLOSED_ERROR);
|
|
607
|
+
for (const entry of [...this[kPendingConfirms]]) {
|
|
608
|
+
this[kPendingConfirms].delete(entry);
|
|
609
|
+
entry.resolveSettled(err);
|
|
610
|
+
if (typeof entry.userCallback === 'function') entry.userCallback(err);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
549
613
|
}
|
|
550
614
|
|
|
551
615
|
class FakeAmqplibConnection extends events.EventEmitter {
|
|
@@ -588,6 +652,10 @@ class FakeAmqplibConnection extends events.EventEmitter {
|
|
|
588
652
|
this._channels.push(channel);
|
|
589
653
|
return resolveOrCallback(args.slice(-1)[0], null, channel);
|
|
590
654
|
}
|
|
655
|
+
updateSecret(...args) {
|
|
656
|
+
process.nextTick(() => this.emit('update-secret-ok'));
|
|
657
|
+
return resolveOrCallback(args.slice(-1)[0]);
|
|
658
|
+
}
|
|
591
659
|
close(...args) {
|
|
592
660
|
if (this[kClosed]) return resolveOrCallback(args.slice(-1)[0]);
|
|
593
661
|
this[kClosed] = true;
|
|
@@ -610,6 +678,8 @@ function FakeAmqplib(minorVersion = '3.5') {
|
|
|
610
678
|
|
|
611
679
|
this.connect = this.connect.bind(this);
|
|
612
680
|
this.connectSync = this.connectSync.bind(this);
|
|
681
|
+
this.connectWithRecoveryPromise = this.connectWithRecoveryPromise.bind(this);
|
|
682
|
+
this.connectWithRecoveryCallback = this.connectWithRecoveryCallback.bind(this);
|
|
613
683
|
this.resetMock = this.resetMock.bind(this);
|
|
614
684
|
this.setVersion = this.setVersion.bind(this);
|
|
615
685
|
}
|
|
@@ -619,6 +689,19 @@ FakeAmqplib.prototype.connect = function fakeConnect(amqpUrl, ...args) {
|
|
|
619
689
|
return resolveOrCallback(args.slice(-1)[0], null, connection);
|
|
620
690
|
};
|
|
621
691
|
|
|
692
|
+
FakeAmqplib.prototype.connectWithRecoveryPromise = function fakeConnectWithRecoveryPromise(amqpUrl, recoveryOptions, socketOptions) {
|
|
693
|
+
return this.connect(amqpUrl, socketOptions);
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
FakeAmqplib.prototype.connectWithRecoveryCallback = function fakeConnectWithRecoveryCallback(
|
|
697
|
+
amqpUrl,
|
|
698
|
+
recoveryOptions,
|
|
699
|
+
socketOptions,
|
|
700
|
+
callback
|
|
701
|
+
) {
|
|
702
|
+
return this.connect(amqpUrl, socketOptions, callback);
|
|
703
|
+
};
|
|
704
|
+
|
|
622
705
|
FakeAmqplib.prototype.connectSync = function fakeConnectSync(amqpUrl, ...args) {
|
|
623
706
|
const { _broker } = this.connections.find((conn) => compareConnectionString(conn._url, amqpUrl)) || {};
|
|
624
707
|
const broker = _broker || new AmqplibBroker(this);
|
|
@@ -764,6 +847,14 @@ function connectSync(amqpUrl, ...args) {
|
|
|
764
847
|
return defaultFake.connectSync(amqpUrl, ...args);
|
|
765
848
|
}
|
|
766
849
|
|
|
850
|
+
function connectWithRecoveryPromise(amqpUrl, recoveryOptions, socketOptions) {
|
|
851
|
+
return defaultFake.connectWithRecoveryPromise(amqpUrl, recoveryOptions, socketOptions);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function connectWithRecoveryCallback(amqpUrl, recoveryOptions, socketOptions, callback) {
|
|
855
|
+
return defaultFake.connectWithRecoveryCallback(amqpUrl, recoveryOptions, socketOptions, callback);
|
|
856
|
+
}
|
|
857
|
+
|
|
767
858
|
function resetMock() {
|
|
768
859
|
return defaultFake.resetMock();
|
|
769
860
|
}
|
|
@@ -778,6 +869,8 @@ exports.FakeAmqplibConfirmChannel = FakeAmqplibConfirmChannel;
|
|
|
778
869
|
exports.FakeAmqplibConnection = FakeAmqplibConnection;
|
|
779
870
|
exports.connect = connect;
|
|
780
871
|
exports.connectSync = connectSync;
|
|
872
|
+
exports.connectWithRecoveryCallback = connectWithRecoveryCallback;
|
|
873
|
+
exports.connectWithRecoveryPromise = connectWithRecoveryPromise;
|
|
781
874
|
exports.connections = connections;
|
|
782
875
|
exports.resetMock = resetMock;
|
|
783
876
|
exports.setVersion = setVersion;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onify/fake-amqplib",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Fake amqplib",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -13,19 +13,21 @@
|
|
|
13
13
|
"main": "./main.cjs",
|
|
14
14
|
"scripts": {
|
|
15
15
|
"test": "mocha",
|
|
16
|
-
"posttest": "npm run lint && npm run dist",
|
|
16
|
+
"posttest": "npm run lint && npm run dist && npm run test:md",
|
|
17
17
|
"lint": "eslint . --cache && prettier . -c --cache",
|
|
18
18
|
"dist": "rollup -c",
|
|
19
19
|
"prepack": "npm run dist",
|
|
20
20
|
"cov:html": "c8 -r html -r text mocha",
|
|
21
|
-
"test:lcov": "c8 -r lcov mocha && npm run lint"
|
|
21
|
+
"test:lcov": "c8 -r lcov mocha && npm run lint",
|
|
22
|
+
"test:md": "texample ./README.md -c ./.mocharc.json -r scripts/texample-mocha.js",
|
|
23
|
+
"toc": "node scripts/toc.js"
|
|
22
24
|
},
|
|
23
25
|
"author": {
|
|
24
26
|
"name": "Onify",
|
|
25
27
|
"url": "https://github.com/onify"
|
|
26
28
|
},
|
|
27
29
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
30
|
+
"node": ">=18"
|
|
29
31
|
},
|
|
30
32
|
"repository": {
|
|
31
33
|
"type": "git",
|
|
@@ -33,21 +35,30 @@
|
|
|
33
35
|
},
|
|
34
36
|
"license": "MIT",
|
|
35
37
|
"dependencies": {
|
|
36
|
-
"smqp": "^
|
|
38
|
+
"smqp": "^12.0.0"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
41
|
+
"@eslint/js": "^10.0.1",
|
|
39
42
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
40
|
-
"@types/
|
|
41
|
-
"amqplib": "^
|
|
42
|
-
"c8": "^
|
|
43
|
+
"@types/node": "^20.19.40",
|
|
44
|
+
"amqplib": "^1.2.0",
|
|
45
|
+
"c8": "^11.0.0",
|
|
43
46
|
"chai": "^6.2.1",
|
|
44
|
-
"eslint": "^
|
|
45
|
-
"globals": "^
|
|
47
|
+
"eslint": "^10.3.0",
|
|
48
|
+
"globals": "^17.0.0",
|
|
46
49
|
"mocha": "^11.0.1",
|
|
47
50
|
"prettier": "^3.2.5",
|
|
48
51
|
"quibble": "^0.9.2",
|
|
49
52
|
"rollup": "^4.0.2",
|
|
50
|
-
"texample": "^
|
|
53
|
+
"texample": "^1.0.2"
|
|
54
|
+
},
|
|
55
|
+
"overrides": {
|
|
56
|
+
"c8": {
|
|
57
|
+
"yargs": "^18.0.0"
|
|
58
|
+
},
|
|
59
|
+
"mocha": {
|
|
60
|
+
"yargs": "^18.0.0"
|
|
61
|
+
}
|
|
51
62
|
},
|
|
52
63
|
"keywords": [
|
|
53
64
|
"fake",
|