amqp-suite 0.1.0 β 0.1.2
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 +205 -45
- package/package.json +13 -5
- package/src/{index.d.ts β amqp-client.d.ts} +1 -1
- package/src/amqp-client.js +10 -3
- package/src/index.js +1 -1
- package/.github/workflows/npm-publish.yml +0 -33
- package/docs/assets/repository_banner.png +0 -0
- package/tests/amqp.test.js +0 -172
- package/vitest.config.js +0 -8
package/README.md
CHANGED
|
@@ -1,114 +1,274 @@
|
|
|
1
1
|
# amqp-suite
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/amqp-suite)
|
|
4
|
-
[](https://www.npmjs.com/package/amqp-suite)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.npmjs.com/package/amqp-suite)
|
|
6
6
|
[](https://github.com/iamcarlosdaniel/amqp-suite)
|
|
7
7
|
|
|
8
|
-

|
|
8
|
+

|
|
9
9
|
|
|
10
10
|
`amqp-suite` is a simple and efficient AMQP (Advanced Message Queuing Protocol) client wrapper for Node.js that handles connection management, message publishing, and consuming messages from queues with a topic exchange. This package abstracts complex connection handling and simplifies AMQP usage in applications by providing easy-to-use methods for connecting, publishing, consuming, and gracefully shutting down the connection.
|
|
11
11
|
|
|
12
|
-
## Features
|
|
12
|
+
## π₯ Features
|
|
13
13
|
|
|
14
|
-
- Automatic Reconnection
|
|
15
|
-
- Simplified Pub/Sub
|
|
16
|
-
- Structured Messaging
|
|
17
|
-
- Error Handling
|
|
18
|
-
- Flow Control
|
|
14
|
+
- **Automatic Reconnection:** Built-in retry logic for connection failures and drops.
|
|
15
|
+
- **Simplified Pub/Sub:** Designed for 'topic' exchanges to allow flexible routing.
|
|
16
|
+
- **Structured Messaging:** Automatic JSON serialization and deserialization.
|
|
17
|
+
- **Error Handling:** Graceful handling of malformed messages and channel crashes.
|
|
18
|
+
- **Flow Control:** Integrated prefetch support to prevent consumer saturation.
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
22
|
+
### Package manager
|
|
23
|
+
|
|
24
|
+
Using npm:
|
|
25
|
+
|
|
22
26
|
```bash
|
|
23
27
|
npm install amqp-suite
|
|
24
28
|
```
|
|
25
29
|
|
|
30
|
+
Using yarn:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
yarn add amqp-suite
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Using pnpm:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm add amqp-suite
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Using bun:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bun add amqp-suit
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Once the package is installed, you can import the library using ES Modules:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
import { AmqpClient } from "amqp-suite";
|
|
52
|
+
```
|
|
53
|
+
|
|
26
54
|
## Quick Start
|
|
27
55
|
|
|
28
56
|
### 1. Initialize and Connect
|
|
29
57
|
|
|
30
|
-
Create an instance of
|
|
58
|
+
Create an instance of `AmqpClient` and establish a connection to your RabbitMQ broker. This prepares the client to publish and consume messages.
|
|
31
59
|
|
|
32
60
|
```javascript
|
|
33
61
|
import { AmqpClient } from "amqp-suite";
|
|
34
62
|
|
|
35
|
-
const amqpClient = new AmqpClient("amqp://localhost", "
|
|
63
|
+
const amqpClient = new AmqpClient("amqp://localhost", "example-exchange");
|
|
36
64
|
|
|
37
|
-
|
|
38
|
-
await amqpClient.connect(5, 2000);
|
|
65
|
+
await amqpClient.connect();
|
|
39
66
|
```
|
|
40
67
|
|
|
41
68
|
### 2. Publish Messages
|
|
42
69
|
|
|
43
|
-
The `publish` method
|
|
70
|
+
The `publish` method automatically stringifies your message and sends it as a persistent buffer, ensuring it wonβt be lost if the broker restarts.
|
|
44
71
|
|
|
45
72
|
```javascript
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
await amqpClient.publish(
|
|
74
|
+
"example.events.hello_world", // Routing Key
|
|
75
|
+
{
|
|
76
|
+
message: "Hello World!",
|
|
77
|
+
},
|
|
78
|
+
{} // Options
|
|
79
|
+
);
|
|
53
80
|
```
|
|
54
81
|
|
|
55
82
|
### 3. Consume Messages
|
|
56
83
|
|
|
57
|
-
The `consume` method automatically
|
|
84
|
+
The `consume` method automatically creates queues, binds them to the exchange, and handles acknowledgments (`ack`/`nack`). You only need to provide the queue name and the function that will process incoming messages.
|
|
58
85
|
|
|
59
86
|
```javascript
|
|
60
87
|
await amqpClient.consume(
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
console.log("Received
|
|
64
|
-
|
|
88
|
+
"example-queue", // Queue
|
|
89
|
+
(msg) => {
|
|
90
|
+
console.log("Received message:", msg);
|
|
91
|
+
},
|
|
92
|
+
{}, // Options
|
|
93
|
+
"example.events.hello_world" // Binding Key
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Example Overview
|
|
98
|
+
|
|
99
|
+
This diagram illustrates how a message is sent from the publisher, routed through the topic exchange, enqueued in the queue, and finally consumed by the consumer.
|
|
100
|
+
|
|
101
|
+

|
|
102
|
+
|
|
103
|
+
Hereβs a full example that connects, publishes, consumes messages, and finally closes the connection.
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
import { AmqpClient } from "amqp-suite";
|
|
107
|
+
|
|
108
|
+
const amqpClient = new AmqpClient("amqp://localhost", "example-exchange");
|
|
109
|
+
|
|
110
|
+
await amqpClient.connect();
|
|
111
|
+
|
|
112
|
+
await amqpClient.publish(
|
|
113
|
+
"example.events.hello_world", // Routing Key
|
|
114
|
+
{
|
|
115
|
+
message: "Hello World!",
|
|
116
|
+
},
|
|
117
|
+
{} // Options
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await amqpClient.consume(
|
|
121
|
+
"example-queue", // Queue
|
|
122
|
+
(msg) => {
|
|
123
|
+
console.log("Received message:", msg);
|
|
65
124
|
},
|
|
66
|
-
{
|
|
67
|
-
"
|
|
125
|
+
{}, // Options
|
|
126
|
+
"example.events.hello_world" // Binding Key
|
|
68
127
|
);
|
|
128
|
+
|
|
129
|
+
await amqpClient.close();
|
|
69
130
|
```
|
|
70
131
|
|
|
132
|
+
> **Note:** You can check the full example in [examples/hello-world](https://github.com/iamcarlosdaniel/amqp-suite/tree/main/examples/hello-world).
|
|
133
|
+
|
|
71
134
|
## API Reference
|
|
72
135
|
|
|
73
136
|
### `new AmqpClient(amqpUrl, exchange)`
|
|
74
137
|
|
|
75
|
-
|
|
76
|
-
|
|
138
|
+
Creates a new instance of the AMQP client.
|
|
139
|
+
|
|
140
|
+
The client uses a **durable topic exchange** to enable flexible message routing using routing patterns.
|
|
141
|
+
|
|
142
|
+
#### Parameters
|
|
143
|
+
|
|
144
|
+
- **`amqpUrl`** (`string`)
|
|
145
|
+
The AMQP connection URL.
|
|
146
|
+
Example: `amqp://user:pass@localhost:5672`
|
|
147
|
+
|
|
148
|
+
- **`exchange`** (`string`)
|
|
149
|
+
The name of the **topic exchange** used for publishing and consuming messages.
|
|
150
|
+
The exchange is asserted as `durable`.
|
|
151
|
+
|
|
152
|
+
---
|
|
77
153
|
|
|
78
154
|
### `.connect(retries = 5, delay = 5000)`
|
|
79
155
|
|
|
80
|
-
Establishes
|
|
156
|
+
Establishes a connection to the AMQP broker and creates a channel.
|
|
157
|
+
If the connection is lost unexpectedly, the client will automatically attempt to reconnect.
|
|
158
|
+
|
|
159
|
+
#### Parameters
|
|
160
|
+
|
|
161
|
+
- **`retries`** (`number`, optional)
|
|
162
|
+
Maximum number of reconnection attempts during the initial connection.
|
|
163
|
+
Default: `5`
|
|
164
|
+
|
|
165
|
+
- **`delay`** (`number`, optional)
|
|
166
|
+
Delay in milliseconds between reconnection attempts.
|
|
167
|
+
Default: `5000`
|
|
81
168
|
|
|
82
|
-
|
|
83
|
-
|
|
169
|
+
#### Behavior
|
|
170
|
+
|
|
171
|
+
- Prevents multiple simultaneous connection attempts.
|
|
172
|
+
- Automatically reconnects if the connection is closed by the broker.
|
|
173
|
+
- Reconnection attempts triggered after a connection drop do **not** reuse the original retry counter.
|
|
174
|
+
|
|
175
|
+
#### Returns
|
|
176
|
+
|
|
177
|
+
- `Promise<void>`
|
|
178
|
+
|
|
179
|
+
---
|
|
84
180
|
|
|
85
181
|
### `.publish(routingKey, message, options = {})`
|
|
86
182
|
|
|
87
|
-
Publishes a message to the configured exchange
|
|
183
|
+
Publishes a message to the configured topic exchange using the specified routing key.
|
|
184
|
+
|
|
185
|
+
Messages are automatically serialized to JSON and published as **persistent** by default.
|
|
186
|
+
|
|
187
|
+
#### Parameters
|
|
188
|
+
|
|
189
|
+
- **`routingKey`** (`string`)
|
|
190
|
+
The routing key used to route the message.
|
|
191
|
+
Example: `user.events.create`
|
|
192
|
+
|
|
193
|
+
- **`message`** (`object`)
|
|
194
|
+
The message payload. It will be automatically serialized to JSON.
|
|
195
|
+
|
|
196
|
+
- **`options`** (`object`, optional)
|
|
197
|
+
Additional publish options supported by `amqplib`.
|
|
198
|
+
These options are merged with `{ persistent: true }`.
|
|
199
|
+
|
|
200
|
+
#### Behavior
|
|
88
201
|
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
|
|
202
|
+
- If the channel is not initialized, the client will attempt to connect automatically.
|
|
203
|
+
- If the brokerβs write buffer is full, the message may be temporarily buffered locally.
|
|
204
|
+
|
|
205
|
+
#### Returns
|
|
206
|
+
|
|
207
|
+
- `Promise<void>`
|
|
208
|
+
|
|
209
|
+
---
|
|
92
210
|
|
|
93
211
|
### `.consume(queue, onMessage, options = {}, bindingKey = "#")`
|
|
94
212
|
|
|
95
|
-
Consumes messages from the specified queue
|
|
213
|
+
Consumes messages from the specified queue and binds it to the exchange using the provided routing pattern.
|
|
214
|
+
|
|
215
|
+
The `onMessage` callback is executed for each received message.
|
|
216
|
+
|
|
217
|
+
#### Parameters
|
|
96
218
|
|
|
97
|
-
-
|
|
98
|
-
|
|
219
|
+
- **`queue`** (`string`)
|
|
220
|
+
The name of the queue to consume messages from.
|
|
221
|
+
The queue is asserted as `durable`.
|
|
222
|
+
|
|
223
|
+
- **`onMessage`** (`function`)
|
|
224
|
+
An asynchronous callback executed when a message is received.
|
|
99
225
|
|
|
100
226
|
```javascript
|
|
101
227
|
async (content, rawMessage) => {
|
|
102
|
-
|
|
228
|
+
// message handling logic
|
|
103
229
|
};
|
|
104
230
|
```
|
|
105
231
|
|
|
106
|
-
-
|
|
107
|
-
-
|
|
232
|
+
- `content`: Parsed JSON message payload.
|
|
233
|
+
- `rawMessage`: The original `ConsumeMessage` from `amqplib`.
|
|
234
|
+
|
|
235
|
+
- **`options`** (`object`, optional)
|
|
236
|
+
Consumer configuration options.
|
|
237
|
+
|
|
238
|
+
- **`prefetch`** (`number`):
|
|
239
|
+
Limits the number of unacknowledged messages.
|
|
240
|
+
Default: `10`
|
|
241
|
+
|
|
242
|
+
- **`bindingKey`** (`string`, optional)
|
|
243
|
+
The routing pattern used to bind the queue to the exchange.
|
|
244
|
+
Default: `#` (matches all routing keys).
|
|
245
|
+
|
|
246
|
+
#### Behavior
|
|
247
|
+
|
|
248
|
+
- Messages are acknowledged (`ack`) automatically after successful processing.
|
|
249
|
+
- If an error is thrown while processing a message:
|
|
250
|
+
|
|
251
|
+
- The message is negatively acknowledged (`nack`)
|
|
252
|
+
- The message is **not requeued**, preventing infinite retry loops for malformed messages.
|
|
253
|
+
|
|
254
|
+
#### Returns
|
|
255
|
+
|
|
256
|
+
- `Promise<void>`
|
|
257
|
+
|
|
258
|
+
---
|
|
108
259
|
|
|
109
260
|
### `.close()`
|
|
110
261
|
|
|
111
|
-
Gracefully closes the channel and
|
|
262
|
+
Gracefully closes the AMQP channel and connection.
|
|
263
|
+
|
|
264
|
+
#### Behavior
|
|
265
|
+
|
|
266
|
+
- Prevents automatic reconnection during shutdown.
|
|
267
|
+
- Ensures resources are released cleanly.
|
|
268
|
+
|
|
269
|
+
#### Returns
|
|
270
|
+
|
|
271
|
+
- `Promise<void>`
|
|
112
272
|
|
|
113
273
|
## License
|
|
114
274
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amqp-suite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A simple wrapper for AMQP 0-9-1 messaging.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Carlos Daniel Menchaca Arauz",
|
|
8
8
|
"email": "contact@iamcarlosdaniel.com",
|
|
9
|
-
"url": "
|
|
9
|
+
"url": "https://iamcarlosdaniel.com"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/iamcarlosdaniel/amqp-suite.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://iamcarlosdaniel.com/projects/amqp-suite",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/iamcarlosdaniel/amqp-suite/issues/new"
|
|
10
18
|
},
|
|
11
19
|
"type": "module",
|
|
12
20
|
"main": "src/index.js",
|
|
@@ -27,10 +35,10 @@
|
|
|
27
35
|
"rabbitmq"
|
|
28
36
|
],
|
|
29
37
|
"dependencies": {
|
|
30
|
-
"amqplib": "
|
|
38
|
+
"amqplib": "0.10.9"
|
|
31
39
|
},
|
|
32
40
|
"devDependencies": {
|
|
33
|
-
"@vitest/ui": "
|
|
34
|
-
"vitest": "
|
|
41
|
+
"@vitest/ui": "4.0.16",
|
|
42
|
+
"vitest": "4.0.16"
|
|
35
43
|
}
|
|
36
44
|
}
|
package/src/amqp-client.js
CHANGED
|
@@ -23,6 +23,8 @@ class AmqpClient {
|
|
|
23
23
|
this.channel = null;
|
|
24
24
|
/** @type {boolean} State flag to prevent multiple simultaneous connection attempts. */
|
|
25
25
|
this.isConnecting = false;
|
|
26
|
+
/** @type {boolean} State flag to indicate if the client is closing. */
|
|
27
|
+
this.isClosing = false;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -51,11 +53,14 @@ class AmqpClient {
|
|
|
51
53
|
});
|
|
52
54
|
|
|
53
55
|
this.connection.on("close", () => {
|
|
54
|
-
this.isConnecting = false;
|
|
55
56
|
this.connection = null;
|
|
56
57
|
this.channel = null;
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
if (!this.isClosing) {
|
|
60
|
+
this.isConnecting = false;
|
|
61
|
+
console.warn(`AMQP connection lost. Retrying in ${delay}ms...`);
|
|
62
|
+
setTimeout(() => this.connect(), delay);
|
|
63
|
+
}
|
|
59
64
|
});
|
|
60
65
|
|
|
61
66
|
console.log("AMQP: Connection established successfully");
|
|
@@ -171,10 +176,12 @@ class AmqpClient {
|
|
|
171
176
|
*/
|
|
172
177
|
async close() {
|
|
173
178
|
try {
|
|
179
|
+
this.isClosing = true;
|
|
174
180
|
await this.channel?.close();
|
|
175
181
|
await this.connection?.close();
|
|
176
182
|
console.log("AMQP: Connection closed cleanly.");
|
|
177
183
|
} catch (err) {
|
|
184
|
+
this.isClosing = false;
|
|
178
185
|
console.error("AMQP: Error during shutdown:", err);
|
|
179
186
|
}
|
|
180
187
|
}
|
package/src/index.js
CHANGED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
-
|
|
4
|
-
name: Node.js Package
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
release:
|
|
8
|
-
types: [created]
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
build:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
- uses: actions/setup-node@v4
|
|
16
|
-
with:
|
|
17
|
-
node-version: 20
|
|
18
|
-
- run: npm ci
|
|
19
|
-
- run: npm run test:run
|
|
20
|
-
|
|
21
|
-
publish-npm:
|
|
22
|
-
needs: build
|
|
23
|
-
runs-on: ubuntu-latest
|
|
24
|
-
steps:
|
|
25
|
-
- uses: actions/checkout@v4
|
|
26
|
-
- uses: actions/setup-node@v4
|
|
27
|
-
with:
|
|
28
|
-
node-version: 20
|
|
29
|
-
registry-url: https://registry.npmjs.org/
|
|
30
|
-
- run: npm ci
|
|
31
|
-
- run: npm publish
|
|
32
|
-
env:
|
|
33
|
-
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
|
Binary file
|
package/tests/amqp.test.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Unit tests for the AMQP class using Vitest and amqplib mocks.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
6
|
-
import amqp from "amqplib";
|
|
7
|
-
import { AmqpClient } from "amqp-suite";
|
|
8
|
-
|
|
9
|
-
vi.mock("amqplib");
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Test suite for AMQP integration and message handling.
|
|
13
|
-
*/
|
|
14
|
-
describe("AMQP Class", () => {
|
|
15
|
-
/** @type {AmqpClient} */
|
|
16
|
-
let amqpInstance;
|
|
17
|
-
|
|
18
|
-
/** @type {string} */
|
|
19
|
-
const mockUrl = "amqp://localhost";
|
|
20
|
-
|
|
21
|
-
/** @type {string} */
|
|
22
|
-
const mockExchange = "test_exchange";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Mocked AMQP Channel object with Vitest spy functions.
|
|
26
|
-
* @type {Object}
|
|
27
|
-
*/
|
|
28
|
-
const mockChannel = {
|
|
29
|
-
assertExchange: vi.fn().mockResolvedValue(undefined),
|
|
30
|
-
assertQueue: vi.fn().mockResolvedValue({}),
|
|
31
|
-
bindQueue: vi.fn().mockResolvedValue(undefined),
|
|
32
|
-
prefetch: vi.fn().mockResolvedValue(undefined),
|
|
33
|
-
publish: vi.fn().mockReturnValue(true),
|
|
34
|
-
consume: vi.fn().mockResolvedValue({ consumerTag: "abc" }),
|
|
35
|
-
ack: vi.fn(),
|
|
36
|
-
nack: vi.fn(),
|
|
37
|
-
close: vi.fn().mockResolvedValue(undefined),
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Mocked AMQP Connection object.
|
|
42
|
-
* @type {Object}
|
|
43
|
-
*/
|
|
44
|
-
const mockConnection = {
|
|
45
|
-
createChannel: vi.fn().mockResolvedValue(mockChannel),
|
|
46
|
-
on: vi.fn(),
|
|
47
|
-
close: vi.fn().mockResolvedValue(undefined),
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Setup hook to reset mocks and re-initialize the AMQP instance before each test.
|
|
52
|
-
*/
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
vi.clearAllMocks();
|
|
55
|
-
amqpInstance = new AmqpClient(mockUrl, mockExchange);
|
|
56
|
-
amqp.connect.mockResolvedValue(mockConnection);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Test case: Constructor property assignment.
|
|
61
|
-
*/
|
|
62
|
-
it("should correctly initialize values in the constructor", () => {
|
|
63
|
-
expect(amqpInstance.amqpUrl).toBe(mockUrl);
|
|
64
|
-
expect(amqpInstance.exchange).toBe(mockExchange);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Test case: Connection workflow and exchange assertion.
|
|
69
|
-
*/
|
|
70
|
-
it("should connect and create a channel successfully", async () => {
|
|
71
|
-
await amqpInstance.connect();
|
|
72
|
-
|
|
73
|
-
expect(amqp.connect).toHaveBeenCalledWith(mockUrl);
|
|
74
|
-
expect(mockConnection.createChannel).toHaveBeenCalled();
|
|
75
|
-
expect(mockChannel.assertExchange).toHaveBeenCalledWith(
|
|
76
|
-
mockExchange,
|
|
77
|
-
"topic",
|
|
78
|
-
{ durable: true }
|
|
79
|
-
);
|
|
80
|
-
expect(amqpInstance.connection).not.toBeNull();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Test case: Message publishing logic and buffer conversion.
|
|
85
|
-
*/
|
|
86
|
-
it("should publish a message correctly", async () => {
|
|
87
|
-
await amqpInstance.connect();
|
|
88
|
-
const routingKey = "user.created";
|
|
89
|
-
const message = { id: 1, name: "Test" };
|
|
90
|
-
|
|
91
|
-
await amqpInstance.publish(routingKey, message);
|
|
92
|
-
|
|
93
|
-
expect(mockChannel.publish).toHaveBeenCalledWith(
|
|
94
|
-
mockExchange,
|
|
95
|
-
routingKey,
|
|
96
|
-
expect.any(Buffer),
|
|
97
|
-
expect.objectContaining({ persistent: true })
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Test case: Consumer setup and successful message processing.
|
|
103
|
-
*/
|
|
104
|
-
it("should setup consumer and acknowledge messages on success", async () => {
|
|
105
|
-
await amqpInstance.connect();
|
|
106
|
-
const queueName = "test_queue";
|
|
107
|
-
const bindingKey = "test.key";
|
|
108
|
-
const mockPayload = { data: "hello world" };
|
|
109
|
-
const mockOnMessage = vi.fn().mockResolvedValue(undefined);
|
|
110
|
-
|
|
111
|
-
await amqpInstance.consume(
|
|
112
|
-
queueName,
|
|
113
|
-
mockOnMessage,
|
|
114
|
-
{ prefetch: 5 },
|
|
115
|
-
bindingKey
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
expect(mockChannel.assertQueue).toHaveBeenCalledWith(
|
|
119
|
-
queueName,
|
|
120
|
-
expect.objectContaining({ durable: true })
|
|
121
|
-
);
|
|
122
|
-
expect(mockChannel.bindQueue).toHaveBeenCalledWith(
|
|
123
|
-
queueName,
|
|
124
|
-
mockExchange,
|
|
125
|
-
bindingKey
|
|
126
|
-
);
|
|
127
|
-
expect(mockChannel.prefetch).toHaveBeenCalledWith(5);
|
|
128
|
-
|
|
129
|
-
const consumerCallback = mockChannel.consume.mock.calls[0][1];
|
|
130
|
-
const fakeMsg = {
|
|
131
|
-
content: Buffer.from(JSON.stringify(mockPayload)),
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
await consumerCallback(fakeMsg);
|
|
135
|
-
|
|
136
|
-
expect(mockOnMessage).toHaveBeenCalledWith(mockPayload, fakeMsg);
|
|
137
|
-
expect(mockChannel.ack).toHaveBeenCalledWith(fakeMsg);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Test case: Consumer error handling (nack).
|
|
142
|
-
*/
|
|
143
|
-
it("should nack messages if the processing fails", async () => {
|
|
144
|
-
await amqpInstance.connect();
|
|
145
|
-
const mockOnMessage = vi
|
|
146
|
-
.fn()
|
|
147
|
-
.mockRejectedValue(new Error("Processing failed"));
|
|
148
|
-
|
|
149
|
-
await amqpInstance.consume("error_queue", mockOnMessage);
|
|
150
|
-
|
|
151
|
-
const consumerCallback = mockChannel.consume.mock.calls[0][1];
|
|
152
|
-
const fakeMsg = {
|
|
153
|
-
content: Buffer.from(JSON.stringify({ some: "data" })),
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
await consumerCallback(fakeMsg);
|
|
157
|
-
|
|
158
|
-
expect(mockChannel.nack).toHaveBeenCalledWith(fakeMsg, false, false);
|
|
159
|
-
expect(mockChannel.ack).not.toHaveBeenCalled();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Test case: Graceful shutdown of channel and connection.
|
|
164
|
-
*/
|
|
165
|
-
it("should close the connection cleanly", async () => {
|
|
166
|
-
await amqpInstance.connect();
|
|
167
|
-
await amqpInstance.close();
|
|
168
|
-
|
|
169
|
-
expect(mockChannel.close).toHaveBeenCalled();
|
|
170
|
-
expect(mockConnection.close).toHaveBeenCalled();
|
|
171
|
-
});
|
|
172
|
-
});
|