@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/README.md +89 -29
- package/index.d.ts +60 -0
- package/index.js +451 -360
- package/main.cjs +591 -0
- package/package.json +30 -13
- package/CHANGELOG.md +0 -70
package/index.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import { Broker } from 'smqp';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { format as urlFormat } from 'url';
|
|
2
4
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
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
|
|
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
|
-
|
|
25
|
+
export class FakeAmqplibChannel {
|
|
26
|
+
constructor(broker, connection) {
|
|
27
|
+
this.connection = connection;
|
|
25
28
|
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
166
|
+
const args = [ this._broker.sendToQueue, queue, content ];
|
|
102
167
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
203
|
+
const exchange = this.getExchange(source);
|
|
204
|
+
if (!exchange) throw new FakeAmqpNotFoundError('exchange', source);
|
|
125
205
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
220
|
+
return this._callBroker(check, callback);
|
|
226
221
|
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
if (!exchange) throw new FakeAmqpNotFoundError('exchange', source);
|
|
328
|
+
args.push(...addConfirmCallback(this._broker, options, callback));
|
|
272
329
|
|
|
273
|
-
|
|
274
|
-
|
|
330
|
+
this.checkExchange(exchange).then(() => {
|
|
331
|
+
return this._callBroker(...args);
|
|
332
|
+
}).catch((err) => {
|
|
333
|
+
this[kEmitter].emit('error', err);
|
|
334
|
+
});
|
|
275
335
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
-
|
|
287
|
-
if (!exchange) throw new FakeAmqpNotFoundError('exchange', source);
|
|
343
|
+
args.push(...addConfirmCallback(this._broker, options, callback));
|
|
288
344
|
|
|
289
|
-
|
|
290
|
-
|
|
345
|
+
this.checkQueue(queue).then(() => {
|
|
346
|
+
return this._callBroker(...args);
|
|
347
|
+
}).catch((err) => {
|
|
348
|
+
this[kEmitter].emit('error', err);
|
|
349
|
+
});
|
|
291
350
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
390
|
-
}
|
|
402
|
+
this._channels.splice(0).forEach((channel) => channel.close());
|
|
391
403
|
|
|
392
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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[
|
|
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
|
|
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 +=
|
|
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
|
+
}
|