@onify/fake-amqplib 1.0.0 → 2.0.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/index.js CHANGED
@@ -1,17 +1,18 @@
1
- 'use strict';
1
+ import { Broker } from 'smqp';
2
+ import { EventEmitter } from 'events';
3
+ import { format as urlFormat } from 'url';
2
4
 
3
- const {Broker} = require('smqp');
4
- const {EventEmitter} = require('events');
5
- const {URL, format: urlFormat} = require('url');
6
-
7
- const smqpSymbol = Symbol.for('smqp');
5
+ const kSmqp = Symbol.for('smqp');
6
+ const kClosed = Symbol.for('closed');
7
+ const kEmitter = Symbol.for('event emitter');
8
+ const kPrefetch = Symbol.for('prefetch');
8
9
 
9
10
  class FakeAmqpError extends Error {
10
11
  constructor(message, code, killChannel, killConnection) {
11
12
  super(message);
12
13
  this.code = code;
13
14
  this._killChannel = killChannel;
14
- this._killConnection = killConnection;
15
+ this._killConnection = killConnection;
15
16
  }
16
17
  }
17
18
 
@@ -21,405 +22,444 @@ class FakeAmqpNotFoundError extends FakeAmqpError {
21
22
  }
22
23
  }
23
24
 
24
- const connections = [];
25
+ export class FakeAmqplibChannel {
26
+ constructor(broker, connection) {
27
+ this.connection = connection;
25
28
 
26
- module.exports = Fake('3.5');
29
+ this[kPrefetch] = 10000;
30
+ this[kClosed] = false;
31
+ this[kEmitter] = new EventEmitter();
32
+ this._channelName = `channel-${generateId()}`;
33
+ this._version = connection._version;
34
+ this._broker = broker;
27
35
 
28
- function Fake(minorVersion) {
29
- let defaultVersion = Number(minorVersion);
30
- return {
31
- connections,
32
- resetMock,
33
- connectSync,
34
- setVersion(minor) {
35
- const n = Number(minor);
36
- if (!isNaN(n)) defaultVersion = n;
37
- },
38
- connect,
39
- };
36
+ this._emitReturn = this._emitReturn.bind(this);
40
37
 
41
- function connect(amqpUrl, ...args) {
42
- const connection = connectSync(amqpUrl, ...args);
43
- return resolveOrCallback(args.slice(-1)[0], null, connection);
38
+ broker.on('return', this._emitReturn);
39
+ }
40
+ get _emitter() {
41
+ return this[kEmitter];
42
+ }
43
+ get _closed() {
44
+ return this[kClosed];
44
45
  }
46
+ assertExchange(...args) {
47
+ return this._callBroker(assertExchange, ...args);
45
48
 
46
- function connectSync(amqpUrl, ...args) {
47
- const {_broker} = connections.find((conn) => compareConnectionString(conn._url, amqpUrl)) || {};
48
- const broker = _broker || Broker();
49
- const connection = Connection(broker, defaultVersion, amqpUrl, ...args);
50
- connections.push(connection);
51
- return connection;
49
+ function assertExchange(exchange, ...assertArgs) {
50
+ this.assertExchange(exchange, ...assertArgs);
51
+ return { exchange };
52
+ }
52
53
  }
54
+ assertQueue(...args) {
55
+ const connection = this.connection;
56
+ return this._callBroker(assertQueue, ...args);
57
+
58
+ function assertQueue(queueName, ...assertArgs) {
59
+ const name = queueName ? queueName : `amqp.gen-${generateId()}`;
60
+ const options = typeof assertArgs[0] === 'object' ? assertArgs.shift() : {};
61
+ const queue = this.assertQueue(name, { ...options, _connectionId: connection._id }, ...assertArgs);
62
+ return {
63
+ queue: name,
64
+ messageCount: queue.messageCount,
65
+ consumerCount: queue.consumerCount,
66
+ };
67
+ }
68
+ }
69
+ bindExchange(destination, source, ...args) {
70
+ const broker = this._broker;
71
+ return Promise.all([ this.checkExchange(source), this.checkExchange(destination) ]).then(() => {
72
+ return this._callBroker(broker.bindExchange, source, destination, ...args);
73
+ });
74
+ }
75
+ bindQueue(queue, source, ...args) {
76
+ return Promise.all([ this.checkQueue(queue), this.checkExchange(source) ]).then(() => {
77
+ return this._callBroker(this._broker.bindQueue, queue, source, ...args);
78
+ });
79
+ }
80
+ checkExchange(name, ...args) {
81
+ const connPath = this.connection._url.pathname;
82
+ return this._callBroker(check, ...args);
53
83
 
54
- function resetMock() {
55
- for (const connection of connections.splice(0)) {
56
- connection._broker.reset();
84
+ function check() {
85
+ if (!this.getExchange(name)) throw new FakeAmqpNotFoundError('exchange', name, connPath);
86
+ return true;
57
87
  }
58
88
  }
89
+ checkQueue(name, ...args) {
90
+ const connPath = this.connection._url.pathname;
91
+ return this._callBroker(check, ...args);
92
+
93
+ function check() {
94
+ let queue;
95
+ if (!(queue = this.getQueue(name))) {
96
+ throw new FakeAmqpNotFoundError('queue', name, connPath);
97
+ }
59
98
 
60
- function Connection(broker, version, amqpUrl, ...connArgs) {
61
- const emitter = new EventEmitter();
62
- const options = connArgs.filter((a) => typeof a !== 'function');
63
- let closed = false;
64
- const channels = [];
65
- const url = normalizeAmqpUrl(amqpUrl);
99
+ return {
100
+ messageCount: queue.messageCount,
101
+ consumerCount: queue.consumerCount,
102
+ };
103
+ }
104
+ }
105
+ get(queue, ...args) {
106
+ const connPath = this.connection._url.pathname;
107
+ return this._callBroker(getMessage, ...args);
108
+
109
+ function getMessage(...getargs) {
110
+ const q = this.getQueue(queue);
111
+ if (!q) throw new FakeAmqpNotFoundError('queue', queue, connPath._url.pathname);
112
+ const msg = q.get(...getargs) || false;
113
+ if (!msg) return msg;
114
+ return new Message(msg);
115
+ }
116
+ }
117
+ deleteExchange(exchange, ...args) {
118
+ const connPath = this.connection._url.pathname;
119
+ return this._callBroker(check, ...args);
120
+
121
+ function check() {
122
+ const result = this.deleteExchange(exchange, ...args);
123
+ if (!result && this.owner.version < 3.2) throw new FakeAmqpNotFoundError('exchange', exchange, connPath);
124
+ return result;
125
+ }
126
+ }
127
+ deleteQueue(queue, ...args) {
128
+ const connPath = this.connection._url.pathname;
129
+ return this._callBroker(check, ...args);
130
+
131
+ function check() {
132
+ const result = this.deleteQueue(queue, ...args);
133
+ if (!result && this.owner.version < 3.2) throw new FakeAmqpNotFoundError('queue', queue, connPath);
134
+ return result;
135
+ }
136
+ }
137
+ publish(exchange, routingKey, content, options, callback) {
138
+ if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
139
+ if (exchange === '') return this.sendToQueue(routingKey, content, options, callback);
66
140
 
67
- return {
68
- _id: generateId(),
69
- _broker: broker,
70
- _version: version,
71
- _url: url,
72
- get _closed() {
73
- return closed;
74
- },
75
- get _emitter() {
76
- return emitter;
77
- },
78
- options,
79
- createChannel(...args) {
80
- if (closed) return resolveOrCallback(args.slice(-1)[0], unavailable());
141
+ const args = [ this._broker.publish, exchange, routingKey, content ];
81
142
 
82
- const channel = Channel(broker, this);
83
- channels.push(channel);
84
- return resolveOrCallback(args.slice(-1)[0], null, channel);
85
- },
86
- createConfirmChannel(...args) {
87
- if (closed) return resolveOrCallback(args.slice(-1)[0], unavailable());
143
+ args.push(options, callback);
88
144
 
89
- const channel = Channel(broker, this, true);
90
- channels.push(channel);
91
- return resolveOrCallback(args.slice(-1)[0], null, channel);
92
- },
93
- close(...args) {
94
- if (closed) return resolveOrCallback(args.slice(-1)[0]);
95
- closed = true;
145
+ this.checkExchange(exchange).then(() => {
146
+ return this._callBroker(...args);
147
+ }).catch((err) => {
148
+ this[kEmitter].emit('error', err);
149
+ });
96
150
 
97
- const idx = connections.indexOf(this);
98
- if (idx > -1) connections.splice(idx, 1);
99
- channels.splice(0).forEach((channel) => channel.close());
151
+ return true;
152
+ }
153
+ purgeQueue(queue, ...args) {
154
+ const connPath = this.connection._url.pathname;
155
+ return this._callBroker(check, ...args);
156
+
157
+ function check() {
158
+ const result = this.purgeQueue(queue);
159
+ if (!result && this.owner.version < 3.2) throw new FakeAmqpNotFoundError('queue', queue, connPath);
160
+ return result === undefined ? undefined : { messageCount: result };
161
+ }
162
+ }
163
+ sendToQueue(queue, content, options, callback) {
164
+ if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
100
165
 
101
- emitter.emit('close');
166
+ const args = [ this._broker.sendToQueue, queue, content ];
102
167
 
103
- return resolveOrCallback(args.slice(-1)[0]);
104
- },
105
- on(...args) {
106
- return emitter.on(...args);
107
- },
108
- once(...args) {
109
- return emitter.once(...args);
110
- },
111
- };
168
+ args.push(options, callback);
169
+
170
+ this.checkQueue(queue).then(() => {
171
+ return this._callBroker(...args);
172
+ }).catch((err) => {
173
+ this[kEmitter].emit('error', err);
174
+ });
175
+
176
+ return true;
177
+ }
178
+ unbindExchange(destination, source, pattern, ...args) {
179
+ const connPath = this.connection._url.pathname;
180
+ return this._callBroker(check, ...args);
181
+
182
+ function check() {
183
+ const q = this.getExchange(destination);
184
+ if (!q) throw new FakeAmqpNotFoundError('exchange', destination);
185
+
186
+ const exchange = this.getExchange(source);
187
+ if (!exchange) throw new FakeAmqpNotFoundError('exchange', source);
188
+
189
+ const result = this.unbindExchange(source, destination, pattern);
190
+ if (!result && this.owner.version <= 3.2) throw new FakeAmqpNotFoundError('binding', pattern, connPath);
112
191
 
113
- function unavailable() {
114
- return new FakeAmqpError('Connection closed: 504', 504);
192
+ return true;
115
193
  }
116
194
  }
195
+ unbindQueue(queue, source, pattern, ...args) {
196
+ const connPath = this.connection._url.pathname;
197
+ return this._callBroker(check, ...args);
117
198
 
118
- function Channel(broker, connection, confirmChannel) {
119
- let closed = false, prefetch = 10000;
120
- const emitter = new EventEmitter();
121
- const channelName = 'channel-' + generateId();
122
- const version = connection._version;
199
+ function check() {
200
+ const q = this.getQueue(queue);
201
+ if (!q) throw new FakeAmqpNotFoundError('queue', queue);
123
202
 
124
- broker.on('return', emitReturn);
203
+ const exchange = this.getExchange(source);
204
+ if (!exchange) throw new FakeAmqpNotFoundError('exchange', source);
125
205
 
126
- return {
127
- _broker: broker,
128
- _version: version,
129
- get _emitter() {
130
- return emitter;
131
- },
132
- get _closed() {
133
- return closed;
134
- },
135
- assertExchange(...args) {
136
- return callBroker(assertExchange, ...args);
137
-
138
- function assertExchange(exchange, ...args) {
139
- broker.assertExchange(exchange, ...args);
140
- return {
141
- exchange,
142
- };
143
- }
144
- },
145
- assertQueue(...args) {
146
- return callBroker(assertQueue, ...args);
147
-
148
- function assertQueue(queueName, ...args) {
149
- const name = queueName ? queueName : 'amqp.gen-' + generateId();
150
- const options = typeof args[0] === 'object' ? args.shift() : {};
151
- const queue = broker.assertQueue(name, {...options, _connectionId: connection._id}, ...args);
152
- return {
153
- queue: name,
154
- messageCount: queue.messageCount,
155
- consumerCount: queue.consumerCount,
156
- };
157
- }
158
- },
159
- bindExchange(destination, source, ...args) {
160
- return Promise.all([this.checkExchange(source), this.checkExchange(destination)]).then(() => {
161
- return callBroker(broker.bindExchange, source, destination, ...args);
162
- });
163
- },
164
- bindQueue(queue, source, ...args) {
165
- return Promise.all([this.checkQueue(queue), this.checkExchange(source)]).then(() => {
166
- return callBroker(broker.bindQueue, queue, source, ...args);
167
- });
168
- },
169
- checkExchange(name, ...args) {
170
- return callBroker(check, ...args);
206
+ const binding = exchange.getBinding(queue, pattern);
207
+ if (!binding && this.owner.version <= 3.2) {
208
+ throw new FakeAmqpNotFoundError('binding', pattern, connPath, this.owner.version < 3.2);
209
+ }
171
210
 
172
- function check() {
173
- if (!broker.getExchange(name)) throw new FakeAmqpNotFoundError('exchange', name, connection._url.pathname);
174
- return true;
175
- }
176
- },
177
- checkQueue(name, ...args) {
178
- return callBroker(check, ...args);
179
-
180
- function check() {
181
- let queue;
182
- if (!(queue = broker.getQueue(name))) {
183
- throw new FakeAmqpNotFoundError('queue', name, connection._url.pathname);
184
- }
185
-
186
- return {
187
- messageCount: queue.messageCount,
188
- consumerCount: queue.consumerCount,
189
- };
190
- }
191
- },
192
- get(queue, ...args) {
193
- return callBroker(getMessage, ...args);
194
-
195
- function getMessage(...getargs) {
196
- const q = broker.getQueue(queue);
197
- if (!q) throw new FakeAmqpNotFoundError('queue', queue, connection._url.pathname);
198
- const msg = q.get(...getargs) || false;
199
- if (!msg) return msg;
200
- return new Message(msg);
201
- }
202
- },
203
- deleteExchange(exchange, ...args) {
204
- return callBroker(check, ...args);
205
-
206
- function check() {
207
- const result = broker.deleteExchange(exchange, ...args);
208
- if (!result && version < 3.2) throw new FakeAmqpNotFoundError('exchange', exchange, connection._url.pathname);
209
- return result;
210
- }
211
- },
212
- deleteQueue(queue, ...args) {
213
- return callBroker(check, ...args);
214
-
215
- function check() {
216
- const result = broker.deleteQueue(queue, ...args);
217
- if (!result && version < 3.2) throw new FakeAmqpNotFoundError('queue', queue, connection._url.pathname);
218
- return result;
219
- }
220
- },
221
- publish(exchange, routingKey, content, options, callback) {
222
- if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
223
- if (exchange === '') return this.sendToQueue(routingKey, content, options, callback);
211
+ this.unbindQueue(queue, source, pattern);
212
+ return true;
213
+ }
214
+ }
215
+ consume(queue, onMessage, options = {}, callback) {
216
+ const { _id: connId, _url: connUrl } = this.connection;
217
+ const channelName = this._channelName;
218
+ const prefetch = this[kPrefetch];
224
219
 
225
- const args = [broker.publish, exchange, routingKey, content];
220
+ return this._callBroker(check, callback);
226
221
 
227
- if (confirmChannel) args.push(...addConfirmCallback(options, callback));
228
- else args.push(options);
222
+ function check() {
223
+ const q = queue && this.getQueue(queue);
224
+ if (!q) {
225
+ throw new FakeAmqpNotFoundError('queue', queue, connUrl.pathname);
226
+ }
229
227
 
230
- this.checkExchange(exchange).then(() => {
231
- return callBroker(...args);
232
- }).catch((err) => {
233
- emitter.emit('error', err);
234
- });
228
+ if (q.exclusive || (q.options.exclusive && q.options._connectionId !== connId)) {
229
+ throw new FakeAmqpError(`Channel closed by server: 403 (ACCESS-REFUSED) with message "ACCESS_REFUSED - queue '${queue}' in vhost '${connUrl.pathname}' in exclusive use"`, 403, true, true);
230
+ }
235
231
 
236
- return true;
237
- },
238
- purgeQueue(queue, ...args) {
239
- return callBroker(check, ...args);
240
-
241
- function check() {
242
- const result = broker.purgeQueue(queue);
243
- if (!result && version < 3.2) throw new FakeAmqpNotFoundError('queue', queue, connection._url.pathname);
244
- return result === undefined ? undefined : {messageCount: result};
245
- }
246
- },
247
- sendToQueue(queue, content, options, callback) {
248
- if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
232
+ const { consumerTag } = this.consume(queue, onMessage && handler, {
233
+ ...options,
234
+ channelName,
235
+ prefetch,
236
+ });
237
+ return { consumerTag };
238
+ }
249
239
 
250
- const args = [broker.sendToQueue, queue, content];
240
+ function handler(_, msg) {
241
+ onMessage(new Message(msg));
242
+ }
243
+ }
244
+ cancel(consumerTag, ...args) {
245
+ return this._callBroker(this._broker.cancel, consumerTag, ...args);
246
+ }
247
+ close(callback) {
248
+ if (this[kClosed]) return;
249
+ const channelName = this._channelName;
250
+ const broker = this._broker;
251
+
252
+ broker.off('return', this._emitReturn);
253
+ const channelConsumers = broker.getConsumers().filter((f) => f.options.channelName === channelName);
254
+ channelConsumers.forEach((c) => broker.cancel(c.consumerTag));
255
+ this[kClosed] = true;
256
+ this[kEmitter].emit('close');
257
+ return resolveOrCallback(callback);
258
+ }
259
+ ack(message, ...args) {
260
+ this._broker.ack(message[kSmqp], ...args);
261
+ }
262
+ ackAll() {
263
+ const broker = this._broker;
264
+ const channelName = this._channelName;
251
265
 
252
- if (confirmChannel) args.push(...addConfirmCallback(options, callback));
253
- else args.push(options);
266
+ const consumers = broker.getConsumers().filter(({ options }) => options.channelName === channelName);
267
+ consumers.forEach((c) => broker.getConsumer(c.consumerTag).ackAll());
268
+ }
269
+ nack(message, ...args) {
270
+ if (this.connection._version >= 2.3) throw new Error(`Nack is not implemented in versions before 2.3 (${this.connection._version})`);
271
+ return this._broker.nack(message[kSmqp], ...args);
272
+ }
273
+ reject(message, ...args) {
274
+ this._broker.reject(message[kSmqp], ...args);
275
+ }
276
+ nackAll(requeue = false) {
277
+ const broker = this._broker;
278
+ const channelName = this._channelName;
254
279
 
255
- this.checkQueue(queue).then(() => {
256
- return callBroker(...args);
257
- }).catch((err) => {
258
- emitter.emit('error', err);
259
- });
280
+ const consumers = broker.getConsumers().filter(({ options }) => options.channelName === channelName);
281
+ consumers.forEach((c) => broker.getConsumer(c.consumerTag).nackAll(requeue));
282
+ }
283
+ prefetch(val) {
284
+ this[kPrefetch] = val;
285
+ }
286
+ on(...args) {
287
+ return this[kEmitter].on(...args);
288
+ }
289
+ once(...args) {
290
+ return this[kEmitter].once(...args);
291
+ }
292
+ _callBroker(fn, ...args) {
293
+ let [ poppedCb ] = args.slice(-1);
294
+ if (typeof poppedCb === 'function') args.splice(-1);
295
+ else poppedCb = null;
296
+
297
+ if (this.connection._closed) throw new FakeAmqpError('Connection is closed', 504);
298
+ if (this[kClosed]) throw new Error('Channel is closed');
299
+
300
+ return new Promise((resolve, reject) => {
301
+ try {
302
+ const result = fn.call(this._broker, ...args);
303
+ if (poppedCb) poppedCb(null, result);
304
+ return resolve(result);
305
+ } catch (err) {
306
+ if (err._killConnection) this.connection.close();
307
+ else if (err._killChannel) this[kClosed] = true;
308
+ if (!poppedCb) return reject(err);
309
+ poppedCb(err);
310
+ return resolve();
311
+ }
312
+ });
313
+ }
314
+ _emitReturn({ fields, content, properties }) {
315
+ process.nextTick(() => {
316
+ this[kEmitter].emit('return', { fields, content, properties });
317
+ });
318
+ }
319
+ }
260
320
 
261
- return true;
262
- },
263
- unbindExchange(destination, source, pattern, ...args) {
264
- return callBroker(check, ...args);
321
+ export class FakeAmqplibConfirmChannel extends FakeAmqplibChannel {
322
+ publish(exchange, routingKey, content, options, callback) {
323
+ if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
324
+ if (exchange === '') return this.sendToQueue(routingKey, content, options, callback);
265
325
 
266
- function check() {
267
- const q = broker.getExchange(destination);
268
- if (!q) throw new FakeAmqpNotFoundError('exchange', destination);
326
+ const args = [ this._broker.publish, exchange, routingKey, content ];
269
327
 
270
- const exchange = broker.getExchange(source);
271
- if (!exchange) throw new FakeAmqpNotFoundError('exchange', source);
328
+ args.push(...addConfirmCallback(this._broker, options, callback));
272
329
 
273
- const result = broker.unbindExchange(source, destination, pattern);
274
- if (!result && version <= 3.2) throw new FakeAmqpNotFoundError('binding', pattern, connection._url.pathname);
330
+ this.checkExchange(exchange).then(() => {
331
+ return this._callBroker(...args);
332
+ }).catch((err) => {
333
+ this[kEmitter].emit('error', err);
334
+ });
275
335
 
276
- return true;
277
- }
278
- },
279
- unbindQueue(queue, source, pattern, ...args) {
280
- return callBroker(check, ...args);
336
+ return true;
337
+ }
338
+ sendToQueue(queue, content, options, callback) {
339
+ if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
281
340
 
282
- function check() {
283
- const q = broker.getQueue(queue);
284
- if (!q) throw new FakeAmqpNotFoundError('queue', queue);
341
+ const args = [ this._broker.sendToQueue, queue, content ];
285
342
 
286
- const exchange = broker.getExchange(source);
287
- if (!exchange) throw new FakeAmqpNotFoundError('exchange', source);
343
+ args.push(...addConfirmCallback(this._broker, options, callback));
288
344
 
289
- const binding = exchange.getBinding(queue, pattern);
290
- if (!binding && version <= 3.2) throw new FakeAmqpNotFoundError('binding', pattern, connection._url.pathname, version < 3.2);
345
+ this.checkQueue(queue).then(() => {
346
+ return this._callBroker(...args);
347
+ }).catch((err) => {
348
+ this[kEmitter].emit('error', err);
349
+ });
291
350
 
292
- broker.unbindQueue(queue, source, pattern);
293
- return true;
294
- }
295
- },
296
- consume(queue, onMessage, options = {}, callback) {
297
- return callBroker(check, callback);
298
-
299
- function check() {
300
- const q = queue && broker.getQueue(queue);
301
- if (!q) {
302
- throw new FakeAmqpNotFoundError('queue', queue, connection._url.pathname);
303
- }
304
-
305
- if (q.exclusive || (q.options.exclusive && q.options._connectionId !== connection._id)) {
306
- throw new FakeAmqpError(`Channel closed by server: 403 (ACCESS-REFUSED) with message "ACCESS_REFUSED - queue '${queue}' in vhost '${connection._url.pathname}' in exclusive use"`, 403, true, true);
307
- }
308
-
309
- const {consumerTag} = broker.consume(queue, onMessage && handler, {...options, channelName, prefetch});
310
- return {consumerTag};
311
- }
312
-
313
- function handler(_, msg) {
314
- onMessage(new Message(msg));
315
- }
316
- },
317
- cancel(consumerTag, ...args) {
318
- return callBroker(broker.cancel, consumerTag, ...args);
319
- },
320
- close(callback) {
321
- if (closed) return;
322
- broker.off('return', emitReturn);
323
- const channelConsumers = broker.getConsumers().filter((f) => f.options.channelName === channelName);
324
- channelConsumers.forEach((c) => broker.cancel(c.consumerTag));
325
- closed = true;
326
- emitter.emit('close');
327
- return resolveOrCallback(callback);
328
- },
329
- ack(message, ...args) {
330
- broker.ack(message[smqpSymbol], ...args);
331
- },
332
- ackAll() {
333
- const consumers = broker.getConsumers().filter(({options}) => options.channelName === channelName);
334
- consumers.forEach((c) => broker.getConsumer(c.consumerTag).ackAll());
335
- },
336
- ...(version >= 2.3 ? {
337
- nack(message, ...args) {
338
- return broker.nack(message[smqpSymbol], ...args);
339
- },
340
- } : undefined),
341
- reject(message, ...args) {
342
- broker.reject(message[smqpSymbol], ...args);
343
- },
344
- nackAll(requeue = false) {
345
- const consumers = broker.getConsumers().filter(({options}) => options.channelName === channelName);
346
- consumers.forEach((c) => broker.getConsumer(c.consumerTag).nackAll(requeue));
347
- },
348
- prefetch(val) {
349
- prefetch = val;
350
- },
351
- on(...args) {
352
- return emitter.on(...args);
353
- },
354
- once(...args) {
355
- return emitter.once(...args);
351
+ return true;
352
+ }
353
+ }
354
+
355
+ export class FakeAmqplibConnection {
356
+ constructor(broker, version, amqpUrl) {
357
+ this[kEmitter] = new EventEmitter();
358
+ this[kClosed] = false;
359
+ this._channels = [];
360
+ this._url = normalizeAmqpUrl(amqpUrl);
361
+ this._id = generateId();
362
+ this._broker = broker;
363
+ this._version = version;
364
+ }
365
+ get _closed() {
366
+ return this[kClosed];
367
+ }
368
+ get _emitter() {
369
+ return this[kEmitter];
370
+ }
371
+ get connection() {
372
+ return {
373
+ serverProperties: {
374
+ host: this._url.host,
375
+ product: 'RabbitMQ',
376
+ version: `${this._version.toString()}.0`,
377
+ platform: 'OS',
378
+ copyright: 'MIT',
379
+ information: 'fake',
356
380
  },
357
381
  };
382
+ }
383
+ createChannel(...args) {
384
+ const callback = args.slice(-1)[0];
385
+ if (this[kClosed]) return resolveOrCallback(callback, new FakeAmqpError('Connection closed: 504', 504));
358
386
 
359
- function addConfirmCallback(options, callback) {
360
- const confirm = 'msg.' + generateId();
361
- const consumerTag = 'ct-' + confirm;
362
- options = {...options, confirm};
363
-
364
- broker.on('message.*', onConsumeMessage, {consumerTag});
365
-
366
- let undelivered;
367
- function onConsumeMessage(event) {
368
- if (event.properties && event.properties.confirm !== confirm) return;
369
- switch(event.name) {
370
- case 'message.nack':
371
- case 'message.undelivered':
372
- undelivered = event.name;
373
- break;
374
- }
375
- }
387
+ const channel = new FakeAmqplibChannel(this._broker, this);
388
+ this._channels.push(channel);
389
+ return resolveOrCallback(callback, null, channel);
390
+ }
391
+ createConfirmChannel(...args) {
392
+ if (this[kClosed]) return resolveOrCallback(args.slice(-1)[0], new FakeAmqpError('Connection closed: 504', 504));
376
393
 
377
- function confirmCallback() {
378
- broker.off('message.*', consumerTag);
379
- switch (undelivered) {
380
- case 'message.nack':
381
- return callback(new Error('message nacked'));
382
- case 'message.undelivered':
383
- throw callback(new Error('message undelivered'));
384
- default:
385
- return callback(null, true);
386
- }
387
- }
394
+ const channel = new FakeAmqplibConfirmChannel(this._broker, this);
395
+ this._channels.push(channel);
396
+ return resolveOrCallback(args.slice(-1)[0], null, channel);
397
+ }
398
+ close(...args) {
399
+ if (this[kClosed]) return resolveOrCallback(args.slice(-1)[0]);
400
+ this[kClosed] = true;
388
401
 
389
- return [options, confirmCallback];
390
- }
402
+ this._channels.splice(0).forEach((channel) => channel.close());
391
403
 
392
- function callBroker(fn, ...args) {
393
- let [poppedCb] = args.slice(-1);
394
- if (typeof poppedCb === 'function') args.splice(-1);
395
- else poppedCb = null;
396
-
397
- if (connection._closed) throw new FakeAmqpError('Connection is closed', 504);
398
- if (closed) throw new Error('Channel is closed');
399
-
400
- return new Promise((resolve, reject) => {
401
- try {
402
- const result = fn.call(broker, ...args);
403
- if (poppedCb) poppedCb(null, result);
404
- return resolve(result);
405
- } catch (err) {
406
- if (err._killConnection) connection.close();
407
- else if (err._killChannel) closed = true;
408
- if (!poppedCb) return reject(err);
409
- poppedCb(err);
410
- return resolve();
411
- }
412
- });
413
- }
404
+ this[kEmitter].emit('close');
414
405
 
415
- function emitReturn({fields, content, properties}) {
416
- process.nextTick(() => {
417
- emitter.emit('return', {fields, content, properties});
418
- });
419
- }
406
+ return resolveOrCallback(args.slice(-1)[0]);
407
+ }
408
+ on(...args) {
409
+ return this[kEmitter].on(...args);
410
+ }
411
+ once(...args) {
412
+ return this[kEmitter].once(...args);
420
413
  }
421
414
  }
422
415
 
416
+ export function FakeAmqplib(minorVersion = '3.5') {
417
+ if (!(this instanceof FakeAmqplib)) {
418
+ return new FakeAmqplib(minorVersion);
419
+ }
420
+
421
+ this.version = Number(minorVersion);
422
+ this.connections = [];
423
+
424
+ this.connect = this.connect.bind(this);
425
+ this.connectSync = this.connectSync.bind(this);
426
+ this.resetMock = this.resetMock.bind(this);
427
+ this.setVersion = this.setVersion.bind(this);
428
+ }
429
+
430
+ FakeAmqplib.prototype.connect = function fakeConnect(amqpUrl, ...args) {
431
+ const connection = this.connectSync(amqpUrl, ...args);
432
+ return resolveOrCallback(args.slice(-1)[0], null, connection);
433
+ };
434
+
435
+ FakeAmqplib.prototype.connectSync = function fakeConnectSync(amqpUrl, ...args) {
436
+ const { _broker } = this.connections.find((conn) => compareConnectionString(conn._url, amqpUrl)) || {};
437
+ const broker = _broker || new Broker(this);
438
+ const connection = new FakeAmqplibConnection(broker, this.version, amqpUrl, ...args);
439
+
440
+ const connections = this.connections;
441
+
442
+ connections.push(connection);
443
+
444
+ connection._emitter.once('close', () => {
445
+ const idx = connections.indexOf(connection);
446
+ if (idx > -1) connections.splice(idx, 1);
447
+ });
448
+
449
+ return connection;
450
+ };
451
+
452
+ FakeAmqplib.prototype.resetMock = function fakeResetMock() {
453
+ for (const connection of this.connections.splice(0)) {
454
+ connection._broker.reset();
455
+ }
456
+ };
457
+
458
+ FakeAmqplib.prototype.setVersion = function fakeSetVersion(minorVersion) {
459
+ const n = Number(minorVersion);
460
+ if (!isNaN(n)) this.version = n;
461
+ };
462
+
423
463
  function resolveOrCallback(optionalCb, err, ...args) {
424
464
  if (typeof optionalCb === 'function') optionalCb(err, ...args);
425
465
  if (err) return Promise.reject(err);
@@ -438,14 +478,14 @@ function compareConnectionString(url1, url2) {
438
478
  }
439
479
 
440
480
  function Message(smqpMessage) {
441
- this[smqpSymbol] = smqpMessage;
481
+ this[kSmqp] = smqpMessage;
442
482
  this.content = smqpMessage.content;
443
483
  this.fields = smqpMessage.fields;
444
484
  this.properties = smqpMessage.properties;
445
485
  }
446
486
 
447
487
  function normalizeAmqpUrl(url) {
448
- if (!url) return url = new URL('amqp://localhost:5672/');
488
+ if (!url) return new URL('amqp://localhost:5672/');
449
489
  if (typeof url === 'string') url = new URL(url);
450
490
 
451
491
  if (!(url instanceof URL)) {
@@ -460,7 +500,7 @@ function normalizeAmqpUrl(url) {
460
500
  } = url;
461
501
  let auth = username;
462
502
  if (auth && password) {
463
- auth += ':' + password;
503
+ auth += `:${password}`;
464
504
  }
465
505
  url = new URL(urlFormat({
466
506
  protocol,
@@ -486,3 +526,54 @@ function normalizeAmqpUrl(url) {
486
526
  if (!url.pathname) url.pathname = '/';
487
527
  return url;
488
528
  }
529
+
530
+ function addConfirmCallback(broker, options, callback) {
531
+ const confirm = `msg.${generateId()}`;
532
+ const consumerTag = `ct-${confirm}`;
533
+ options = { ...options, confirm };
534
+
535
+ broker.on('message.*', onConsumeMessage, { consumerTag });
536
+
537
+ let undelivered;
538
+ function onConsumeMessage(event) {
539
+ switch (event.name) {
540
+ case 'message.nack':
541
+ case 'message.undelivered':
542
+ undelivered = event.name;
543
+ break;
544
+ }
545
+ }
546
+
547
+ function confirmCallback() {
548
+ broker.off('message.*', consumerTag);
549
+ switch (undelivered) {
550
+ case 'message.nack':
551
+ return callback(new Error('message nacked'));
552
+ case 'message.undelivered':
553
+ throw callback(new Error('message undelivered'));
554
+ default:
555
+ return callback(null, true);
556
+ }
557
+ }
558
+
559
+ return [ options, confirmCallback ];
560
+ }
561
+
562
+ const defaultFake = new FakeAmqplib('3.5');
563
+ export const connections = defaultFake.connections;
564
+
565
+ export function connect(amqpUrl, ...args) {
566
+ return defaultFake.connect(amqpUrl, ...args);
567
+ }
568
+
569
+ export function connectSync(amqpUrl, ...args) {
570
+ return defaultFake.connectSync(amqpUrl, ...args);
571
+ }
572
+
573
+ export function resetMock() {
574
+ return defaultFake.resetMock();
575
+ }
576
+
577
+ export function setVersion(minorVersion) {
578
+ return defaultFake.setVersion(minorVersion);
579
+ }