poly-bus-rabbitmq 0.4.1
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 +209 -0
- package/dist/__tests__/mocks/poly-bus.d.ts +60 -0
- package/dist/__tests__/mocks/poly-bus.js +31 -0
- package/dist/__tests__/transport/rabbitmq/rabbitmq-transport.integration.test.d.ts +1 -0
- package/dist/__tests__/transport/rabbitmq/rabbitmq-transport.integration.test.js +230 -0
- package/dist/__tests__/transport/rabbitmq/rabbitmq-transport.test.d.ts +1 -0
- package/dist/__tests__/transport/rabbitmq/rabbitmq-transport.test.js +257 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/index.mjs +254 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.js +260 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/package.json +1 -0
- package/dist/transport/rabbitmq/rabbitmq-config.d.ts +19 -0
- package/dist/transport/rabbitmq/rabbitmq-config.js +31 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts +27 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.js +230 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# poly-bus-rabbitmq
|
|
2
|
+
|
|
3
|
+
The RabbitMQ transport for PolyBus.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js 18+
|
|
8
|
+
- npm 9+
|
|
9
|
+
- RabbitMQ server (for runtime/integration scenarios)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install poly-bus-rabbitmq poly-bus@0.3.11
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Setting Up Development Environment
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Navigate to the typescript directory
|
|
23
|
+
cd src/typescript
|
|
24
|
+
|
|
25
|
+
# Install dependencies
|
|
26
|
+
npm install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { PolyBusBuilder } from "poly-bus";
|
|
33
|
+
import { RabbitMqConfig } from "poly-bus-rabbitmq";
|
|
34
|
+
|
|
35
|
+
const builder = new PolyBusBuilder("my-service");
|
|
36
|
+
|
|
37
|
+
const rabbitMq = new RabbitMqConfig();
|
|
38
|
+
rabbitMq.connectionString = "amqp://guest:guest@localhost:5672";
|
|
39
|
+
rabbitMq.queueName = "my-service";
|
|
40
|
+
|
|
41
|
+
builder.withTransport(async (b, bus) => rabbitMq.create(b, bus));
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Behavior
|
|
45
|
+
|
|
46
|
+
The transport mirrors the .NET implementation in this repository:
|
|
47
|
+
|
|
48
|
+
- Command, event, and direct exchanges.
|
|
49
|
+
- Dead-letter queue setup.
|
|
50
|
+
- Delayed delivery queues using binary routing segments and TTL dead-letter chaining.
|
|
51
|
+
- Routing key format:
|
|
52
|
+
- Publish: `MessageType.Endpoint.Name.Major.Minor.Patch`
|
|
53
|
+
- Subscribe: `MessageType.Endpoint.Name.Major.#`
|
|
54
|
+
|
|
55
|
+
## Exports
|
|
56
|
+
|
|
57
|
+
- `RabbitMqConfig`
|
|
58
|
+
- `RabbitMqTransport`
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cd src/typescript
|
|
64
|
+
npm install
|
|
65
|
+
npm run build
|
|
66
|
+
npm test
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Building The Project
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd src/typescript
|
|
73
|
+
npm run build
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Running Tests
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Run all tests once
|
|
80
|
+
npm test
|
|
81
|
+
|
|
82
|
+
# Run a specific test file
|
|
83
|
+
npx jest src/__tests__/transport/rabbitmq/rabbitmq-transport.test.ts
|
|
84
|
+
|
|
85
|
+
# Run tests matching a test name pattern
|
|
86
|
+
npx jest -t "formats publish routing keys"
|
|
87
|
+
|
|
88
|
+
# Run RabbitMQ integration tests (creates and deletes its own RabbitMQ virtual host)
|
|
89
|
+
# Optional: set RABBITMQ_MANAGEMENT_URL if management API is not on http://localhost:15672
|
|
90
|
+
RABBITMQ_CONNECTION_STRING=amqp://guest:guest@localhost:5672 \
|
|
91
|
+
npx jest src/__tests__/transport/rabbitmq/rabbitmq-transport.integration.test.ts --runInBand
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Development Workflow
|
|
95
|
+
|
|
96
|
+
### Code Quality
|
|
97
|
+
|
|
98
|
+
Current package scripts include:
|
|
99
|
+
|
|
100
|
+
- `npm run build` for TypeScript compilation
|
|
101
|
+
- `npm test` for Jest test execution
|
|
102
|
+
|
|
103
|
+
If you want stricter checks, common additions are:
|
|
104
|
+
|
|
105
|
+
- `npm run typecheck` using `tsc --noEmit`
|
|
106
|
+
- linting with ESLint
|
|
107
|
+
- formatting with Prettier
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
### RabbitMqConfig
|
|
112
|
+
|
|
113
|
+
Common options:
|
|
114
|
+
|
|
115
|
+
- `connectionString` (required): AMQP connection URI.
|
|
116
|
+
- `queueName`: endpoint queue name.
|
|
117
|
+
- `deadLetterQueueName`: dead-letter queue endpoint.
|
|
118
|
+
- `commandExchangeName`, `eventExchangeName`, `directExchangeName`: exchange naming overrides.
|
|
119
|
+
- `delayDelivery`: delayed message exchange prefix.
|
|
120
|
+
- `networkRecoveryIntervalMs`: reconnect interval.
|
|
121
|
+
|
|
122
|
+
### Routing Behavior
|
|
123
|
+
|
|
124
|
+
Routing keys mirror the .NET implementation:
|
|
125
|
+
|
|
126
|
+
- Publish format: `MessageType.Endpoint.Name.Major.Minor.Patch`
|
|
127
|
+
- Subscribe format: `MessageType.Endpoint.Name.Major.#`
|
|
128
|
+
|
|
129
|
+
## Dependencies
|
|
130
|
+
|
|
131
|
+
### Runtime Dependencies
|
|
132
|
+
|
|
133
|
+
- `amqplib`
|
|
134
|
+
- `poly-bus@0.3.11`
|
|
135
|
+
|
|
136
|
+
### Development Dependencies
|
|
137
|
+
|
|
138
|
+
- `typescript`
|
|
139
|
+
- `jest`
|
|
140
|
+
- `@types/node`
|
|
141
|
+
- `@types/amqplib`
|
|
142
|
+
- `rimraf`
|
|
143
|
+
|
|
144
|
+
## Common Commands
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
cd src/typescript
|
|
148
|
+
|
|
149
|
+
# Install, build, and test
|
|
150
|
+
npm install
|
|
151
|
+
npm run build
|
|
152
|
+
npm test
|
|
153
|
+
|
|
154
|
+
# Clean output
|
|
155
|
+
npm run clean
|
|
156
|
+
|
|
157
|
+
# Inspect generated package files
|
|
158
|
+
npm pack --dry-run
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Troubleshooting
|
|
162
|
+
|
|
163
|
+
### Environment Issues
|
|
164
|
+
|
|
165
|
+
1. Verify Node.js version:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
node --version
|
|
169
|
+
npm --version
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
2. Ensure RabbitMQ is reachable via your connection string.
|
|
173
|
+
|
|
174
|
+
### Build Issues
|
|
175
|
+
|
|
176
|
+
1. Clean and rebuild:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm run clean
|
|
180
|
+
npm run build
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
2. If type declaration or module resolution errors appear, delete `node_modules` and reinstall:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
rm -rf node_modules package-lock.json
|
|
187
|
+
npm install
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Test Issues
|
|
191
|
+
|
|
192
|
+
1. Run one file directly to isolate failures:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
npx jest src/__tests__/transport/rabbitmq/rabbitmq-transport.test.ts
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
2. Use test name filtering to narrow down behavioral regressions.
|
|
199
|
+
|
|
200
|
+
## Contributing
|
|
201
|
+
|
|
202
|
+
1. Run `npm run build` and `npm test` before committing.
|
|
203
|
+
2. Add tests for new transport behavior.
|
|
204
|
+
3. Keep behavior aligned with the .NET and Python transport implementations.
|
|
205
|
+
4. Update README examples and configuration docs when APIs change.
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
See the repository LICENSE file for licensing details.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export declare enum MessageType {
|
|
2
|
+
Event = 0,
|
|
3
|
+
Command = 1
|
|
4
|
+
}
|
|
5
|
+
export declare const Headers: {
|
|
6
|
+
readonly MessageType: "MessageType";
|
|
7
|
+
};
|
|
8
|
+
export declare class MessageInfo {
|
|
9
|
+
readonly type: MessageType;
|
|
10
|
+
readonly endpoint: string;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly major: number;
|
|
13
|
+
readonly minor: number;
|
|
14
|
+
readonly patch: number;
|
|
15
|
+
constructor(type: MessageType, endpoint: string, name: string, major: number, minor: number, patch: number);
|
|
16
|
+
static getAttributeFromHeader(_header: string): MessageInfo | null;
|
|
17
|
+
}
|
|
18
|
+
export declare class IncomingMessage {
|
|
19
|
+
headers: Map<string, string>;
|
|
20
|
+
constructor(_bus: IPolyBus, _body: string, _messageInfo: MessageInfo);
|
|
21
|
+
}
|
|
22
|
+
export interface OutgoingMessage {
|
|
23
|
+
messageInfo: MessageInfo;
|
|
24
|
+
headers: Map<string, string>;
|
|
25
|
+
endpoint?: string;
|
|
26
|
+
body: string;
|
|
27
|
+
deliverAt?: Date;
|
|
28
|
+
}
|
|
29
|
+
export interface Transaction {
|
|
30
|
+
outgoingMessages: OutgoingMessage[];
|
|
31
|
+
}
|
|
32
|
+
export interface IncomingTransaction {
|
|
33
|
+
commit(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export interface OutgoingTransaction extends Transaction {
|
|
36
|
+
}
|
|
37
|
+
export interface IPolyBus {
|
|
38
|
+
name: string;
|
|
39
|
+
properties: Map<string, object>;
|
|
40
|
+
transport: unknown;
|
|
41
|
+
incomingPipeline: unknown[];
|
|
42
|
+
outgoingPipeline: unknown[];
|
|
43
|
+
messages: {
|
|
44
|
+
getHeaderByMessageInfo(messageInfo: MessageInfo): string;
|
|
45
|
+
getTypeByMessageInfo(messageInfo: MessageInfo): new () => unknown;
|
|
46
|
+
getMessageInfo(type: new () => unknown): MessageInfo;
|
|
47
|
+
add(type: new () => unknown): MessageInfo;
|
|
48
|
+
};
|
|
49
|
+
createIncomingTransaction(message: IncomingMessage): Promise<IncomingTransaction>;
|
|
50
|
+
createOutgoingTransaction(): Promise<OutgoingTransaction>;
|
|
51
|
+
send(transaction: Transaction): Promise<void>;
|
|
52
|
+
start(): Promise<void>;
|
|
53
|
+
stop(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
export interface ITransport {
|
|
56
|
+
start(): Promise<void>;
|
|
57
|
+
stop(): Promise<void>;
|
|
58
|
+
subscribe(messageInfo: MessageInfo): Promise<void>;
|
|
59
|
+
handle(transaction: Transaction): Promise<void>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IncomingMessage = exports.MessageInfo = exports.Headers = exports.MessageType = void 0;
|
|
4
|
+
var MessageType;
|
|
5
|
+
(function (MessageType) {
|
|
6
|
+
MessageType[MessageType["Event"] = 0] = "Event";
|
|
7
|
+
MessageType[MessageType["Command"] = 1] = "Command";
|
|
8
|
+
})(MessageType || (exports.MessageType = MessageType = {}));
|
|
9
|
+
exports.Headers = {
|
|
10
|
+
MessageType: "MessageType"
|
|
11
|
+
};
|
|
12
|
+
class MessageInfo {
|
|
13
|
+
constructor(type, endpoint, name, major, minor, patch) {
|
|
14
|
+
this.type = type;
|
|
15
|
+
this.endpoint = endpoint;
|
|
16
|
+
this.name = name;
|
|
17
|
+
this.major = major;
|
|
18
|
+
this.minor = minor;
|
|
19
|
+
this.patch = patch;
|
|
20
|
+
}
|
|
21
|
+
static getAttributeFromHeader(_header) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.MessageInfo = MessageInfo;
|
|
26
|
+
class IncomingMessage {
|
|
27
|
+
constructor(_bus, _body, _messageInfo) {
|
|
28
|
+
this.headers = new Map();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.IncomingMessage = IncomingMessage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const globals_1 = require("@jest/globals");
|
|
8
|
+
const amqplib_1 = __importDefault(require("amqplib"));
|
|
9
|
+
const poly_bus_1 = require("poly-bus");
|
|
10
|
+
const rabbitmq_config_1 = require("../../../transport/rabbitmq/rabbitmq-config");
|
|
11
|
+
const rabbitmq_transport_1 = require("../../../transport/rabbitmq/rabbitmq-transport");
|
|
12
|
+
const baseConnectionString = process.env.RABBITMQ_CONNECTION_STRING ?? 'amqp://guest:guest@localhost:5672';
|
|
13
|
+
function encodePathSegment(value) {
|
|
14
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
15
|
+
}
|
|
16
|
+
function getManagementBaseUrl(connectionString) {
|
|
17
|
+
if (process.env.RABBITMQ_MANAGEMENT_URL) {
|
|
18
|
+
return process.env.RABBITMQ_MANAGEMENT_URL;
|
|
19
|
+
}
|
|
20
|
+
const connection = new URL(connectionString);
|
|
21
|
+
const protocol = connection.protocol === 'amqps:' ? 'https:' : 'http:';
|
|
22
|
+
const port = connection.port === '5671' ? '15671' : '15672';
|
|
23
|
+
return `${protocol}//${connection.hostname}:${port}`;
|
|
24
|
+
}
|
|
25
|
+
async function callManagementApi(managementBaseUrl, username, password, path, method, body) {
|
|
26
|
+
const authToken = Buffer.from(`${username}:${password}`, 'utf8').toString('base64');
|
|
27
|
+
const response = await fetch(`${managementBaseUrl}${path}`, {
|
|
28
|
+
method,
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Basic ${authToken}`,
|
|
31
|
+
'Content-Type': 'application/json'
|
|
32
|
+
},
|
|
33
|
+
body: body ? JSON.stringify(body) : undefined
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const responseText = await response.text();
|
|
37
|
+
throw new Error(`RabbitMQ management API ${method} ${path} failed with ${response.status}: ${responseText}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function createTestVirtualHost(connectionString, vhost) {
|
|
41
|
+
const connection = new URL(connectionString);
|
|
42
|
+
const username = decodeURIComponent(connection.username || 'guest');
|
|
43
|
+
const password = decodeURIComponent(connection.password || 'guest');
|
|
44
|
+
const managementBaseUrl = getManagementBaseUrl(connectionString);
|
|
45
|
+
const encodedVhost = encodePathSegment(vhost);
|
|
46
|
+
const encodedUser = encodePathSegment(username);
|
|
47
|
+
await callManagementApi(managementBaseUrl, username, password, `/api/vhosts/${encodedVhost}`, 'PUT');
|
|
48
|
+
await callManagementApi(managementBaseUrl, username, password, `/api/permissions/${encodedVhost}/${encodedUser}`, 'PUT', {
|
|
49
|
+
configure: '.*',
|
|
50
|
+
write: '.*',
|
|
51
|
+
read: '.*'
|
|
52
|
+
});
|
|
53
|
+
connection.pathname = `/${vhost}`;
|
|
54
|
+
connection.search = '';
|
|
55
|
+
connection.hash = '';
|
|
56
|
+
return connection.toString();
|
|
57
|
+
}
|
|
58
|
+
async function deleteTestVirtualHost(connectionString, vhost) {
|
|
59
|
+
const connection = new URL(connectionString);
|
|
60
|
+
const username = decodeURIComponent(connection.username || 'guest');
|
|
61
|
+
const password = decodeURIComponent(connection.password || 'guest');
|
|
62
|
+
const managementBaseUrl = getManagementBaseUrl(connectionString);
|
|
63
|
+
const encodedVhost = encodePathSegment(vhost);
|
|
64
|
+
await callManagementApi(managementBaseUrl, username, password, `/api/vhosts/${encodedVhost}`, 'DELETE');
|
|
65
|
+
}
|
|
66
|
+
function createHeader(messageInfo) {
|
|
67
|
+
return `${messageInfo.type}.${messageInfo.endpoint}.${messageInfo.name}.${messageInfo.major}.${messageInfo.minor}.${messageInfo.patch}`;
|
|
68
|
+
}
|
|
69
|
+
function createBusStub(name, onIncomingMessage) {
|
|
70
|
+
return {
|
|
71
|
+
name,
|
|
72
|
+
properties: new Map(),
|
|
73
|
+
transport: {},
|
|
74
|
+
incomingPipeline: [],
|
|
75
|
+
outgoingPipeline: [],
|
|
76
|
+
messages: {
|
|
77
|
+
getHeaderByMessageInfo: (messageInfo) => createHeader(messageInfo),
|
|
78
|
+
getTypeByMessageInfo: () => class TestMessage {
|
|
79
|
+
},
|
|
80
|
+
getMessageInfo: () => new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, name, 'created', 1, 0, 0),
|
|
81
|
+
add: () => new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, name, 'created', 1, 0, 0)
|
|
82
|
+
},
|
|
83
|
+
createIncomingTransaction: async (message) => {
|
|
84
|
+
onIncomingMessage(message);
|
|
85
|
+
return {
|
|
86
|
+
commit: async () => undefined
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
createOutgoingTransaction: async () => ({
|
|
90
|
+
outgoingMessages: []
|
|
91
|
+
}),
|
|
92
|
+
send: async (_transaction) => undefined,
|
|
93
|
+
start: async () => undefined,
|
|
94
|
+
stop: async () => undefined
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function withTimeout(promise, timeoutMs = 20000) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const timeout = setTimeout(() => {
|
|
100
|
+
reject(new Error(`Timed out after ${timeoutMs}ms waiting for RabbitMQ message`));
|
|
101
|
+
}, timeoutMs);
|
|
102
|
+
promise
|
|
103
|
+
.then((value) => {
|
|
104
|
+
clearTimeout(timeout);
|
|
105
|
+
resolve(value);
|
|
106
|
+
})
|
|
107
|
+
.catch((error) => {
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
reject(error);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
(0, globals_1.describe)('RabbitMqTransport integration', () => {
|
|
114
|
+
let connectionString;
|
|
115
|
+
let testVirtualHostName;
|
|
116
|
+
let transport;
|
|
117
|
+
let queueName;
|
|
118
|
+
let receivedResolve;
|
|
119
|
+
let receivedReject;
|
|
120
|
+
let receivedMessagePromise;
|
|
121
|
+
let parserSpy;
|
|
122
|
+
let publisherConnection;
|
|
123
|
+
let publisherChannel;
|
|
124
|
+
(0, globals_1.beforeAll)(async () => {
|
|
125
|
+
testVirtualHostName = `ts-rabbitmq-integration-${(0, node_crypto_1.randomUUID)()}`;
|
|
126
|
+
connectionString = await createTestVirtualHost(baseConnectionString, testVirtualHostName);
|
|
127
|
+
});
|
|
128
|
+
(0, globals_1.afterAll)(async () => {
|
|
129
|
+
await deleteTestVirtualHost(baseConnectionString, testVirtualHostName);
|
|
130
|
+
});
|
|
131
|
+
(0, globals_1.beforeEach)(() => {
|
|
132
|
+
queueName = `ts-rabbitmq-integration-${(0, node_crypto_1.randomUUID)()}`;
|
|
133
|
+
receivedMessagePromise = new Promise((resolve, reject) => {
|
|
134
|
+
receivedResolve = resolve;
|
|
135
|
+
receivedReject = reject;
|
|
136
|
+
});
|
|
137
|
+
const bus = createBusStub(queueName, (message) => {
|
|
138
|
+
receivedResolve?.(message);
|
|
139
|
+
});
|
|
140
|
+
const config = new rabbitmq_config_1.RabbitMqConfig();
|
|
141
|
+
config.connectionString = connectionString;
|
|
142
|
+
config.queueName = queueName;
|
|
143
|
+
config.deadLetterQueueName = `${queueName}.dead.letters`;
|
|
144
|
+
config.log = {
|
|
145
|
+
info: globals_1.jest.fn(),
|
|
146
|
+
error: (message, ...meta) => {
|
|
147
|
+
receivedReject?.(new Error([message, ...meta.map((entry) => String(entry))].join(' ')));
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
parserSpy = globals_1.jest.spyOn(poly_bus_1.MessageInfo, 'getAttributeFromHeader').mockImplementation((header) => {
|
|
151
|
+
const parts = header.split('.');
|
|
152
|
+
if (parts.length !== 6) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const [typeText, endpoint, name, major, minor, patch] = parts;
|
|
156
|
+
const parsedType = Number.parseInt(typeText, 10);
|
|
157
|
+
if (Number.isNaN(parsedType)) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return new poly_bus_1.MessageInfo(parsedType, endpoint, name, Number.parseInt(major, 10), Number.parseInt(minor, 10), Number.parseInt(patch, 10));
|
|
161
|
+
});
|
|
162
|
+
transport = new rabbitmq_transport_1.RabbitMqTransport(config, bus);
|
|
163
|
+
publisherConnection = null;
|
|
164
|
+
publisherChannel = null;
|
|
165
|
+
});
|
|
166
|
+
(0, globals_1.afterEach)(async () => {
|
|
167
|
+
parserSpy.mockRestore();
|
|
168
|
+
if (publisherChannel) {
|
|
169
|
+
try {
|
|
170
|
+
await publisherChannel.close();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Ignore shutdown failures in cleanup.
|
|
174
|
+
}
|
|
175
|
+
publisherChannel = null;
|
|
176
|
+
}
|
|
177
|
+
if (publisherConnection) {
|
|
178
|
+
try {
|
|
179
|
+
await publisherConnection.close();
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Ignore shutdown failures in cleanup.
|
|
183
|
+
}
|
|
184
|
+
publisherConnection = null;
|
|
185
|
+
}
|
|
186
|
+
await transport.stop();
|
|
187
|
+
});
|
|
188
|
+
(0, globals_1.it)('creates a real RabbitMQ channel and connection on start', async () => {
|
|
189
|
+
await transport.start();
|
|
190
|
+
(0, globals_1.expect)(transport.connection).not.toBeNull();
|
|
191
|
+
(0, globals_1.expect)(transport.channel).not.toBeNull();
|
|
192
|
+
});
|
|
193
|
+
(0, globals_1.it)('publishes and receives command messages through RabbitMQ', async () => {
|
|
194
|
+
await transport.start();
|
|
195
|
+
const messageInfo = new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Command, queueName, 'created', 1, 0, 0);
|
|
196
|
+
const transaction = {
|
|
197
|
+
outgoingMessages: [
|
|
198
|
+
{
|
|
199
|
+
messageInfo,
|
|
200
|
+
headers: new Map(),
|
|
201
|
+
endpoint: undefined,
|
|
202
|
+
body: JSON.stringify({ id: 'integration-command' }),
|
|
203
|
+
deliverAt: undefined
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
};
|
|
207
|
+
await transport.handle(transaction);
|
|
208
|
+
const incoming = await withTimeout(receivedMessagePromise);
|
|
209
|
+
(0, globals_1.expect)(incoming).toBeInstanceOf(poly_bus_1.IncomingMessage);
|
|
210
|
+
(0, globals_1.expect)(incoming.headers.get(poly_bus_1.Headers.MessageType)).toBe(createHeader(messageInfo));
|
|
211
|
+
});
|
|
212
|
+
(0, globals_1.it)('receives subscribed events published by a RabbitMQ producer', async () => {
|
|
213
|
+
await transport.start();
|
|
214
|
+
const eventInfo = new poly_bus_1.MessageInfo(poly_bus_1.MessageType.Event, 'billing', 'paid', 1, 0, 0);
|
|
215
|
+
await transport.subscribe(eventInfo);
|
|
216
|
+
publisherConnection = await amqplib_1.default.connect(connectionString);
|
|
217
|
+
publisherChannel = await publisherConnection.createChannel();
|
|
218
|
+
await publisherChannel.assertExchange(transport.config.eventExchangeName, 'topic', { durable: true });
|
|
219
|
+
const routingKey = transport.getRoutingKey(false, eventInfo);
|
|
220
|
+
const options = {
|
|
221
|
+
headers: {
|
|
222
|
+
[poly_bus_1.Headers.MessageType]: createHeader(eventInfo)
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
publisherChannel.publish(transport.config.eventExchangeName, routingKey, Buffer.from(JSON.stringify({ id: 'integration-event' }), 'utf8'), options);
|
|
226
|
+
const incoming = await withTimeout(receivedMessagePromise);
|
|
227
|
+
(0, globals_1.expect)(incoming).toBeInstanceOf(poly_bus_1.IncomingMessage);
|
|
228
|
+
(0, globals_1.expect)(incoming.headers.get(poly_bus_1.Headers.MessageType)).toBe(createHeader(eventInfo));
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|