@makebelieve21213-packages/rabbitmq-client 1.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/LICENSE +21 -0
- package/README.md +461 -0
- package/dist/__tests__/__mocks__/logger.d.ts +10 -0
- package/dist/__tests__/__mocks__/logger.d.ts.map +1 -0
- package/dist/__tests__/__mocks__/logger.js +11 -0
- package/dist/__tests__/__mocks__/logger.js.map +1 -0
- package/dist/__tests__/__mocks__/redis-client.d.ts +9 -0
- package/dist/__tests__/__mocks__/redis-client.d.ts.map +1 -0
- package/dist/__tests__/__mocks__/redis-client.js +10 -0
- package/dist/__tests__/__mocks__/redis-client.js.map +1 -0
- package/dist/__tests__/connect-rabbitmq-receiver.spec.d.ts +2 -0
- package/dist/__tests__/connect-rabbitmq-receiver.spec.d.ts.map +1 -0
- package/dist/__tests__/connect-rabbitmq-receiver.spec.js +206 -0
- package/dist/__tests__/connect-rabbitmq-receiver.spec.js.map +1 -0
- package/dist/__tests__/connect-rabbitmq-receivers.spec.d.ts +2 -0
- package/dist/__tests__/connect-rabbitmq-receivers.spec.d.ts.map +1 -0
- package/dist/__tests__/connect-rabbitmq-receivers.spec.js +139 -0
- package/dist/__tests__/connect-rabbitmq-receivers.spec.js.map +1 -0
- package/dist/__tests__/index.spec.d.ts +2 -0
- package/dist/__tests__/index.spec.d.ts.map +1 -0
- package/dist/__tests__/index.spec.js +206 -0
- package/dist/__tests__/index.spec.js.map +1 -0
- package/dist/__tests__/setup.d.ts +2 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +15 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/test-types.d.ts +52 -0
- package/dist/__tests__/test-types.d.ts.map +1 -0
- package/dist/__tests__/test-types.js +2 -0
- package/dist/__tests__/test-types.js.map +1 -0
- package/dist/config/__tests__/factories.spec.d.ts +2 -0
- package/dist/config/__tests__/factories.spec.d.ts.map +1 -0
- package/dist/config/__tests__/factories.spec.js +168 -0
- package/dist/config/__tests__/factories.spec.js.map +1 -0
- package/dist/config/factories.d.ts +23 -0
- package/dist/config/factories.d.ts.map +1 -0
- package/dist/config/factories.js +123 -0
- package/dist/config/factories.js.map +1 -0
- package/dist/connect-rabbitmq-receiver.d.ts +8 -0
- package/dist/connect-rabbitmq-receiver.d.ts.map +1 -0
- package/dist/connect-rabbitmq-receiver.js +49 -0
- package/dist/connect-rabbitmq-receiver.js.map +1 -0
- package/dist/connect-rabbitmq-receivers.d.ts +8 -0
- package/dist/connect-rabbitmq-receivers.d.ts.map +1 -0
- package/dist/connect-rabbitmq-receivers.js +18 -0
- package/dist/connect-rabbitmq-receivers.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/__tests__/rabbitmq.interceptor.spec.d.ts +2 -0
- package/dist/interceptors/__tests__/rabbitmq.interceptor.spec.d.ts.map +1 -0
- package/dist/interceptors/__tests__/rabbitmq.interceptor.spec.js +647 -0
- package/dist/interceptors/__tests__/rabbitmq.interceptor.spec.js.map +1 -0
- package/dist/interceptors/rabbitmq.interceptor.d.ts +18 -0
- package/dist/interceptors/rabbitmq.interceptor.d.ts.map +1 -0
- package/dist/interceptors/rabbitmq.interceptor.js +107 -0
- package/dist/interceptors/rabbitmq.interceptor.js.map +1 -0
- package/dist/main/__tests__/rabbitmq.module.spec.d.ts +2 -0
- package/dist/main/__tests__/rabbitmq.module.spec.d.ts.map +1 -0
- package/dist/main/__tests__/rabbitmq.module.spec.js +266 -0
- package/dist/main/__tests__/rabbitmq.module.spec.js.map +1 -0
- package/dist/main/__tests__/rabbitmq.service.spec.d.ts +2 -0
- package/dist/main/__tests__/rabbitmq.service.spec.d.ts.map +1 -0
- package/dist/main/__tests__/rabbitmq.service.spec.js +349 -0
- package/dist/main/__tests__/rabbitmq.service.spec.js.map +1 -0
- package/dist/main/rabbitmq.module.d.ts +10 -0
- package/dist/main/rabbitmq.module.d.ts.map +1 -0
- package/dist/main/rabbitmq.module.js +51 -0
- package/dist/main/rabbitmq.module.js.map +1 -0
- package/dist/main/rabbitmq.service.d.ts +26 -0
- package/dist/main/rabbitmq.service.d.ts.map +1 -0
- package/dist/main/rabbitmq.service.js +100 -0
- package/dist/main/rabbitmq.service.js.map +1 -0
- package/dist/types/base.config.d.ts +15 -0
- package/dist/types/base.config.d.ts.map +1 -0
- package/dist/types/base.config.js +2 -0
- package/dist/types/base.config.js.map +1 -0
- package/dist/types/idempotent-message.d.ts +6 -0
- package/dist/types/idempotent-message.d.ts.map +1 -0
- package/dist/types/idempotent-message.js +2 -0
- package/dist/types/idempotent-message.js.map +1 -0
- package/dist/types/message-with-correlation-id.d.ts +10 -0
- package/dist/types/message-with-correlation-id.d.ts.map +1 -0
- package/dist/types/message-with-correlation-id.js +2 -0
- package/dist/types/message-with-correlation-id.js.map +1 -0
- package/dist/types/rabbitmq-config-dlx.d.ts +4 -0
- package/dist/types/rabbitmq-config-dlx.d.ts.map +1 -0
- package/dist/types/rabbitmq-config-dlx.js +2 -0
- package/dist/types/rabbitmq-config-dlx.js.map +1 -0
- package/dist/types/rabbitmq-config-receiver.d.ts +15 -0
- package/dist/types/rabbitmq-config-receiver.d.ts.map +1 -0
- package/dist/types/rabbitmq-config-receiver.js +2 -0
- package/dist/types/rabbitmq-config-receiver.js.map +1 -0
- package/dist/types/rabbitmq-config-retry.d.ts +13 -0
- package/dist/types/rabbitmq-config-retry.d.ts.map +1 -0
- package/dist/types/rabbitmq-config-retry.js +2 -0
- package/dist/types/rabbitmq-config-retry.js.map +1 -0
- package/dist/types/rabbitmq-config-sender.d.ts +14 -0
- package/dist/types/rabbitmq-config-sender.d.ts.map +1 -0
- package/dist/types/rabbitmq-config-sender.js +2 -0
- package/dist/types/rabbitmq-config-sender.js.map +1 -0
- package/dist/types/rabbitmq-module-options.d.ts +17 -0
- package/dist/types/rabbitmq-module-options.d.ts.map +1 -0
- package/dist/types/rabbitmq-module-options.js +2 -0
- package/dist/types/rabbitmq-module-options.js.map +1 -0
- package/dist/types/rabbitmq-receiver-options.d.ts +18 -0
- package/dist/types/rabbitmq-receiver-options.d.ts.map +1 -0
- package/dist/types/rabbitmq-receiver-options.js +2 -0
- package/dist/types/rabbitmq-receiver-options.js.map +1 -0
- package/dist/types/rabbitmq-receiver-subscription.d.ts +19 -0
- package/dist/types/rabbitmq-receiver-subscription.d.ts.map +1 -0
- package/dist/types/rabbitmq-receiver-subscription.js +2 -0
- package/dist/types/rabbitmq-receiver-subscription.js.map +1 -0
- package/dist/types/rabbitmq-receivers-config.d.ts +14 -0
- package/dist/types/rabbitmq-receivers-config.d.ts.map +1 -0
- package/dist/types/rabbitmq-receivers-config.js +2 -0
- package/dist/types/rabbitmq-receivers-config.js.map +1 -0
- package/dist/types/rabbitmq-sender-options.d.ts +10 -0
- package/dist/types/rabbitmq-sender-options.d.ts.map +1 -0
- package/dist/types/rabbitmq-sender-options.js +2 -0
- package/dist/types/rabbitmq-sender-options.js.map +1 -0
- package/dist/types/routing-keys.d.ts +24 -0
- package/dist/types/routing-keys.d.ts.map +1 -0
- package/dist/types/routing-keys.js +29 -0
- package/dist/types/routing-keys.js.map +1 -0
- package/dist/utils/__tests__/injections.spec.d.ts +2 -0
- package/dist/utils/__tests__/injections.spec.d.ts.map +1 -0
- package/dist/utils/__tests__/injections.spec.js +174 -0
- package/dist/utils/__tests__/injections.spec.js.map +1 -0
- package/dist/utils/injections.d.ts +2 -0
- package/dist/utils/injections.d.ts.map +1 -0
- package/dist/utils/injections.js +2 -0
- package/dist/utils/injections.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
import { LoggerService } from "@makebelieve21213-packages/logger";
|
|
2
|
+
import { of, throwError } from "rxjs";
|
|
3
|
+
import RabbitMQIdempotencyInterceptor from "../../interceptors/rabbitmq.interceptor.js";
|
|
4
|
+
jest.mock("@makebelieve21213-packages/logger", () => ({
|
|
5
|
+
LoggerService: jest.fn().mockImplementation(() => ({
|
|
6
|
+
log: jest.fn(),
|
|
7
|
+
error: jest.fn(),
|
|
8
|
+
warn: jest.fn(),
|
|
9
|
+
debug: jest.fn(),
|
|
10
|
+
setContext: jest.fn(),
|
|
11
|
+
})),
|
|
12
|
+
}));
|
|
13
|
+
describe("RabbitMQIdempotencyInterceptor", () => {
|
|
14
|
+
let interceptor;
|
|
15
|
+
let redisService;
|
|
16
|
+
let mockLoggerInstance;
|
|
17
|
+
let executionContext;
|
|
18
|
+
let callHandler;
|
|
19
|
+
const correlationId = "test-correlation-id-123";
|
|
20
|
+
const correlationTimestamp = Date.now();
|
|
21
|
+
const redisKey = `rabbitmq:idempotency:${correlationId}`;
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
redisService = {
|
|
24
|
+
get: jest.fn(),
|
|
25
|
+
set: jest.fn(),
|
|
26
|
+
del: jest.fn(),
|
|
27
|
+
};
|
|
28
|
+
mockLoggerInstance = {
|
|
29
|
+
log: jest.fn(),
|
|
30
|
+
error: jest.fn(),
|
|
31
|
+
warn: jest.fn(),
|
|
32
|
+
debug: jest.fn(),
|
|
33
|
+
setContext: jest.fn(),
|
|
34
|
+
};
|
|
35
|
+
LoggerService.mockImplementation(() => mockLoggerInstance);
|
|
36
|
+
interceptor = new RabbitMQIdempotencyInterceptor(redisService, mockLoggerInstance);
|
|
37
|
+
executionContext = {
|
|
38
|
+
getType: jest.fn(),
|
|
39
|
+
getArgByIndex: jest.fn(),
|
|
40
|
+
switchToRpc: jest.fn(),
|
|
41
|
+
};
|
|
42
|
+
callHandler = {
|
|
43
|
+
handle: jest.fn(),
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
jest.clearAllMocks();
|
|
48
|
+
});
|
|
49
|
+
describe("constructor", () => {
|
|
50
|
+
it("должен инициализировать интерцептор с redisService и установить контекст логгера", (done) => {
|
|
51
|
+
const testRedisService = {
|
|
52
|
+
get: jest.fn(),
|
|
53
|
+
set: jest.fn(),
|
|
54
|
+
del: jest.fn(),
|
|
55
|
+
};
|
|
56
|
+
const testLoggerInstance = {
|
|
57
|
+
log: jest.fn(),
|
|
58
|
+
error: jest.fn(),
|
|
59
|
+
warn: jest.fn(),
|
|
60
|
+
debug: jest.fn(),
|
|
61
|
+
setContext: jest.fn(),
|
|
62
|
+
};
|
|
63
|
+
LoggerService.mockImplementation(() => testLoggerInstance);
|
|
64
|
+
const testInterceptor = new RabbitMQIdempotencyInterceptor(testRedisService, testLoggerInstance);
|
|
65
|
+
expect(testInterceptor).toBeInstanceOf(RabbitMQIdempotencyInterceptor);
|
|
66
|
+
expect(testLoggerInstance.setContext).toHaveBeenCalledWith("RabbitMQIdempotencyInterceptor");
|
|
67
|
+
// Проверяем, что redisService действительно используется через вызов intercept
|
|
68
|
+
const testExecutionContext = {
|
|
69
|
+
getType: jest.fn().mockReturnValue("rpc"),
|
|
70
|
+
getArgByIndex: jest.fn().mockReturnValue({
|
|
71
|
+
correlationId: "test-id",
|
|
72
|
+
correlationTimestamp: Date.now(),
|
|
73
|
+
data: "test",
|
|
74
|
+
}),
|
|
75
|
+
switchToRpc: jest.fn(),
|
|
76
|
+
};
|
|
77
|
+
const testCallHandler = {
|
|
78
|
+
handle: jest.fn().mockReturnValue(of({ result: "success" })),
|
|
79
|
+
};
|
|
80
|
+
testRedisService.get.mockResolvedValue(null);
|
|
81
|
+
testRedisService.set.mockResolvedValue(undefined);
|
|
82
|
+
const result = testInterceptor.intercept(testExecutionContext, testCallHandler);
|
|
83
|
+
result.subscribe({
|
|
84
|
+
next: () => {
|
|
85
|
+
expect(testRedisService.get).toHaveBeenCalled();
|
|
86
|
+
done();
|
|
87
|
+
},
|
|
88
|
+
error: (err) => {
|
|
89
|
+
done(err);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("intercept", () => {
|
|
95
|
+
it("должен пропустить обработку, если контекст не RPC", (done) => {
|
|
96
|
+
executionContext.getType.mockReturnValue("http");
|
|
97
|
+
callHandler.handle.mockReturnValue(of({ result: "success" }));
|
|
98
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
99
|
+
result.subscribe({
|
|
100
|
+
next: (value) => {
|
|
101
|
+
expect(value).toEqual({ result: "success" });
|
|
102
|
+
expect(redisService.get).not.toHaveBeenCalled();
|
|
103
|
+
expect(callHandler.handle).toHaveBeenCalledTimes(1);
|
|
104
|
+
done();
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it("должен пропустить обработку, если в сообщении нет correlationId", (done) => {
|
|
109
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
110
|
+
executionContext.getArgByIndex.mockReturnValue({ data: "test" });
|
|
111
|
+
callHandler.handle.mockReturnValue(of({ result: "success" }));
|
|
112
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
113
|
+
result.subscribe({
|
|
114
|
+
next: (value) => {
|
|
115
|
+
expect(value).toEqual({ result: "success" });
|
|
116
|
+
expect(redisService.get).not.toHaveBeenCalled();
|
|
117
|
+
expect(callHandler.handle).toHaveBeenCalledTimes(1);
|
|
118
|
+
done();
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
it("должен пропустить обработку, если correlationId не является строкой", (done) => {
|
|
123
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
124
|
+
executionContext.getArgByIndex.mockReturnValue({
|
|
125
|
+
correlationId: 123,
|
|
126
|
+
});
|
|
127
|
+
callHandler.handle.mockReturnValue(of({ result: "success" }));
|
|
128
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
129
|
+
result.subscribe({
|
|
130
|
+
next: (value) => {
|
|
131
|
+
expect(value).toEqual({ result: "success" });
|
|
132
|
+
expect(redisService.get).not.toHaveBeenCalled();
|
|
133
|
+
expect(callHandler.handle).toHaveBeenCalledTimes(1);
|
|
134
|
+
done();
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
it("должен обработать дубликат сообщения и вернуть ответ без обработки", (done) => {
|
|
139
|
+
const message = {
|
|
140
|
+
correlationId,
|
|
141
|
+
correlationTimestamp,
|
|
142
|
+
data: "test",
|
|
143
|
+
};
|
|
144
|
+
const ackFn = jest.fn();
|
|
145
|
+
const rmqContext = {
|
|
146
|
+
getChannelRef: jest.fn().mockReturnValue({
|
|
147
|
+
ack: ackFn,
|
|
148
|
+
}),
|
|
149
|
+
getMessage: jest.fn().mockReturnValue({}),
|
|
150
|
+
};
|
|
151
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
152
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
153
|
+
executionContext.switchToRpc = jest.fn().mockReturnValue({
|
|
154
|
+
getContext: jest.fn().mockReturnValue(rmqContext),
|
|
155
|
+
});
|
|
156
|
+
redisService.get.mockResolvedValue(String(correlationTimestamp));
|
|
157
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
158
|
+
result.subscribe({
|
|
159
|
+
next: (value) => {
|
|
160
|
+
expect(value).toEqual({
|
|
161
|
+
duplicate: true,
|
|
162
|
+
correlationId,
|
|
163
|
+
});
|
|
164
|
+
expect(redisService.get).toHaveBeenCalledWith(redisKey);
|
|
165
|
+
expect(mockLoggerInstance.warn).toHaveBeenCalledWith(expect.stringContaining(`Duplicate message detected and skipped [correlationId=${correlationId}`));
|
|
166
|
+
expect(callHandler.handle).not.toHaveBeenCalled();
|
|
167
|
+
expect(ackFn).toHaveBeenCalled();
|
|
168
|
+
done();
|
|
169
|
+
},
|
|
170
|
+
error: (err) => {
|
|
171
|
+
done(err);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
it("должен обработать новое сообщение и сохранить его в Redis", (done) => {
|
|
176
|
+
const message = {
|
|
177
|
+
correlationId,
|
|
178
|
+
correlationTimestamp,
|
|
179
|
+
data: "test",
|
|
180
|
+
};
|
|
181
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
182
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
183
|
+
redisService.get.mockResolvedValue(null);
|
|
184
|
+
redisService.set.mockResolvedValue(undefined);
|
|
185
|
+
callHandler.handle.mockReturnValue(of({ result: "success" }));
|
|
186
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
187
|
+
result.subscribe({
|
|
188
|
+
next: (value) => {
|
|
189
|
+
expect(value).toEqual({ result: "success" });
|
|
190
|
+
expect(redisService.get).toHaveBeenCalledWith(redisKey);
|
|
191
|
+
expect(redisService.set).toHaveBeenCalledWith(redisKey, String(correlationTimestamp), 24 * 60 * 60);
|
|
192
|
+
expect(callHandler.handle).toHaveBeenCalledTimes(1);
|
|
193
|
+
expect(mockLoggerInstance.log).toHaveBeenCalledWith(expect.stringContaining(`Message processed successfully [correlationId=${correlationId}]`));
|
|
194
|
+
done();
|
|
195
|
+
},
|
|
196
|
+
error: (err) => {
|
|
197
|
+
done(err);
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
it("должен удалить ключ из Redis при ошибке обработки сообщения", (done) => {
|
|
202
|
+
const message = {
|
|
203
|
+
correlationId,
|
|
204
|
+
correlationTimestamp,
|
|
205
|
+
data: "test",
|
|
206
|
+
};
|
|
207
|
+
const error = new Error("Processing error");
|
|
208
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
209
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
210
|
+
redisService.get.mockResolvedValue(null);
|
|
211
|
+
redisService.set.mockResolvedValue(undefined);
|
|
212
|
+
redisService.del.mockResolvedValue(1);
|
|
213
|
+
callHandler.handle.mockReturnValue(throwError(() => error));
|
|
214
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
215
|
+
result.subscribe({
|
|
216
|
+
error: (err) => {
|
|
217
|
+
expect(err).toBe(error);
|
|
218
|
+
expect(redisService.del).toHaveBeenCalledWith(redisKey);
|
|
219
|
+
done();
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
it("должен логировать ошибку при неудачном удалении ключа из Redis", (done) => {
|
|
224
|
+
const message = {
|
|
225
|
+
correlationId,
|
|
226
|
+
correlationTimestamp,
|
|
227
|
+
data: "test",
|
|
228
|
+
};
|
|
229
|
+
const error = new Error("Processing error");
|
|
230
|
+
const delError = new Error("Delete error");
|
|
231
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
232
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
233
|
+
redisService.get.mockResolvedValue(null);
|
|
234
|
+
redisService.set.mockResolvedValue(undefined);
|
|
235
|
+
redisService.del.mockRejectedValue(delError);
|
|
236
|
+
callHandler.handle.mockReturnValue(throwError(() => error));
|
|
237
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
238
|
+
result.subscribe({
|
|
239
|
+
error: (err) => {
|
|
240
|
+
expect(err).toBe(error);
|
|
241
|
+
expect(redisService.del).toHaveBeenCalledWith(redisKey);
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
expect(mockLoggerInstance.error).toHaveBeenCalledWith(expect.stringContaining(`Failed to delete idempotency key [correlationId=${correlationId}]`));
|
|
244
|
+
done();
|
|
245
|
+
}, 100);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
it("должен продолжить обработку при ошибке проверки идемпотентности", (done) => {
|
|
250
|
+
const message = {
|
|
251
|
+
correlationId,
|
|
252
|
+
correlationTimestamp,
|
|
253
|
+
data: "test",
|
|
254
|
+
};
|
|
255
|
+
const redisError = new Error("Redis error");
|
|
256
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
257
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
258
|
+
redisService.get.mockRejectedValue(redisError);
|
|
259
|
+
callHandler.handle.mockReturnValue(of({ result: "success" }));
|
|
260
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
261
|
+
result.subscribe({
|
|
262
|
+
next: (value) => {
|
|
263
|
+
expect(value).toEqual({ result: "success" });
|
|
264
|
+
expect(mockLoggerInstance.error).toHaveBeenCalledWith(expect.stringContaining(`Failed to check idempotency [correlationId=${correlationId}]`));
|
|
265
|
+
expect(callHandler.handle).toHaveBeenCalledTimes(1);
|
|
266
|
+
done();
|
|
267
|
+
},
|
|
268
|
+
error: (err) => {
|
|
269
|
+
done(err);
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
it("должен использовать 'unknown' как correlationId при ошибке Redis, если data не имеет correlationId в момент обработки ошибки", (done) => {
|
|
274
|
+
// Создаем объект с correlationId, который проходит проверку hasCorrelationId
|
|
275
|
+
// Но используем Proxy, чтобы при обращении к correlationId в catchError он возвращал undefined
|
|
276
|
+
let correlationIdAccessCount = 0;
|
|
277
|
+
const message = new Proxy({
|
|
278
|
+
correlationId,
|
|
279
|
+
correlationTimestamp,
|
|
280
|
+
data: "test",
|
|
281
|
+
}, {
|
|
282
|
+
get(target, prop) {
|
|
283
|
+
// При первом обращении (в hasCorrelationId) возвращаем correlationId
|
|
284
|
+
// При втором обращении (в catchError) возвращаем undefined
|
|
285
|
+
if (prop === "correlationId") {
|
|
286
|
+
correlationIdAccessCount++;
|
|
287
|
+
if (correlationIdAccessCount > 1) {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return target[prop];
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
const redisError = new Error("Redis error");
|
|
295
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
296
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
297
|
+
redisService.get.mockRejectedValue(redisError);
|
|
298
|
+
callHandler.handle.mockReturnValue(of({ result: "success" }));
|
|
299
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
300
|
+
result.subscribe({
|
|
301
|
+
next: (value) => {
|
|
302
|
+
expect(value).toEqual({ result: "success" });
|
|
303
|
+
// Проверяем, что ошибка была залогирована с "unknown"
|
|
304
|
+
expect(mockLoggerInstance.error).toHaveBeenCalledWith(expect.stringContaining(`Failed to check idempotency [correlationId=unknown]`));
|
|
305
|
+
expect(callHandler.handle).toHaveBeenCalledTimes(1);
|
|
306
|
+
done();
|
|
307
|
+
},
|
|
308
|
+
error: (err) => {
|
|
309
|
+
done(err);
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
it("должен обработать ошибку при подтверждении дубликата сообщения", (done) => {
|
|
314
|
+
const message = {
|
|
315
|
+
correlationId,
|
|
316
|
+
correlationTimestamp,
|
|
317
|
+
data: "test",
|
|
318
|
+
};
|
|
319
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
320
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
321
|
+
redisService.get.mockResolvedValue(String(correlationTimestamp));
|
|
322
|
+
const rmqContext = {
|
|
323
|
+
getChannelRef: jest.fn().mockImplementation(() => {
|
|
324
|
+
throw new Error("Channel error");
|
|
325
|
+
}),
|
|
326
|
+
getMessage: jest.fn(),
|
|
327
|
+
};
|
|
328
|
+
executionContext.switchToRpc = jest.fn().mockReturnValue({
|
|
329
|
+
getContext: jest.fn().mockReturnValue(rmqContext),
|
|
330
|
+
});
|
|
331
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
332
|
+
result.subscribe({
|
|
333
|
+
next: (value) => {
|
|
334
|
+
expect(value).toEqual({
|
|
335
|
+
duplicate: true,
|
|
336
|
+
correlationId,
|
|
337
|
+
});
|
|
338
|
+
expect(mockLoggerInstance.error).toHaveBeenCalledWith(expect.stringContaining("Failed to acknowledge duplicate message"));
|
|
339
|
+
done();
|
|
340
|
+
},
|
|
341
|
+
error: (err) => {
|
|
342
|
+
done(err);
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
it("должен обработать не-Error ошибку при подтверждении дубликата сообщения", (done) => {
|
|
347
|
+
const message = {
|
|
348
|
+
correlationId,
|
|
349
|
+
correlationTimestamp,
|
|
350
|
+
data: "test",
|
|
351
|
+
};
|
|
352
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
353
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
354
|
+
redisService.get.mockResolvedValue(String(correlationTimestamp));
|
|
355
|
+
const rmqContext = {
|
|
356
|
+
getChannelRef: jest.fn().mockImplementation(() => {
|
|
357
|
+
throw "String error";
|
|
358
|
+
}),
|
|
359
|
+
getMessage: jest.fn(),
|
|
360
|
+
};
|
|
361
|
+
executionContext.switchToRpc = jest.fn().mockReturnValue({
|
|
362
|
+
getContext: jest.fn().mockReturnValue(rmqContext),
|
|
363
|
+
});
|
|
364
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
365
|
+
result.subscribe({
|
|
366
|
+
next: (value) => {
|
|
367
|
+
expect(value).toEqual({
|
|
368
|
+
duplicate: true,
|
|
369
|
+
correlationId,
|
|
370
|
+
});
|
|
371
|
+
expect(mockLoggerInstance.error).toHaveBeenCalledWith(expect.stringContaining("Failed to acknowledge duplicate message: String error"));
|
|
372
|
+
done();
|
|
373
|
+
},
|
|
374
|
+
error: (err) => {
|
|
375
|
+
done(err);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
it("должен обработать случай, когда RmqContext не имеет необходимых методов", (done) => {
|
|
380
|
+
const message = {
|
|
381
|
+
correlationId,
|
|
382
|
+
correlationTimestamp,
|
|
383
|
+
data: "test",
|
|
384
|
+
};
|
|
385
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
386
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
387
|
+
redisService.get.mockResolvedValue(String(correlationTimestamp));
|
|
388
|
+
const rmqContext = {};
|
|
389
|
+
executionContext.switchToRpc = jest.fn().mockReturnValue({
|
|
390
|
+
getContext: jest.fn().mockReturnValue(rmqContext),
|
|
391
|
+
});
|
|
392
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
393
|
+
result.subscribe({
|
|
394
|
+
next: (value) => {
|
|
395
|
+
expect(value).toEqual({
|
|
396
|
+
duplicate: true,
|
|
397
|
+
correlationId,
|
|
398
|
+
});
|
|
399
|
+
done();
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
it("должен обработать случай, когда RmqContext имеет getChannelRef, но не имеет getMessage", (done) => {
|
|
404
|
+
const message = {
|
|
405
|
+
correlationId,
|
|
406
|
+
correlationTimestamp,
|
|
407
|
+
data: "test",
|
|
408
|
+
};
|
|
409
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
410
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
411
|
+
redisService.get.mockResolvedValue(String(correlationTimestamp));
|
|
412
|
+
const rmqContext = {
|
|
413
|
+
getChannelRef: jest.fn().mockReturnValue({
|
|
414
|
+
ack: jest.fn(),
|
|
415
|
+
}),
|
|
416
|
+
};
|
|
417
|
+
executionContext.switchToRpc = jest.fn().mockReturnValue({
|
|
418
|
+
getContext: jest.fn().mockReturnValue(rmqContext),
|
|
419
|
+
});
|
|
420
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
421
|
+
result.subscribe({
|
|
422
|
+
next: (value) => {
|
|
423
|
+
expect(value).toEqual({
|
|
424
|
+
duplicate: true,
|
|
425
|
+
correlationId,
|
|
426
|
+
});
|
|
427
|
+
done();
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
it("должен обработать случай, когда RmqContext имеет getMessage, но не имеет getChannelRef", (done) => {
|
|
432
|
+
const message = {
|
|
433
|
+
correlationId,
|
|
434
|
+
correlationTimestamp,
|
|
435
|
+
data: "test",
|
|
436
|
+
};
|
|
437
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
438
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
439
|
+
redisService.get.mockResolvedValue(String(correlationTimestamp));
|
|
440
|
+
const rmqContext = {
|
|
441
|
+
getMessage: jest.fn().mockReturnValue({}),
|
|
442
|
+
};
|
|
443
|
+
executionContext.switchToRpc = jest.fn().mockReturnValue({
|
|
444
|
+
getContext: jest.fn().mockReturnValue(rmqContext),
|
|
445
|
+
});
|
|
446
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
447
|
+
result.subscribe({
|
|
448
|
+
next: (value) => {
|
|
449
|
+
expect(value).toEqual({
|
|
450
|
+
duplicate: true,
|
|
451
|
+
correlationId,
|
|
452
|
+
});
|
|
453
|
+
done();
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
it("должен обработать случай, когда RmqContext имеет методы, но они не являются функциями", (done) => {
|
|
458
|
+
const message = {
|
|
459
|
+
correlationId,
|
|
460
|
+
correlationTimestamp,
|
|
461
|
+
data: "test",
|
|
462
|
+
};
|
|
463
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
464
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
465
|
+
redisService.get.mockResolvedValue(String(correlationTimestamp));
|
|
466
|
+
const rmqContext = {
|
|
467
|
+
getChannelRef: "not a function",
|
|
468
|
+
getMessage: "not a function",
|
|
469
|
+
};
|
|
470
|
+
executionContext.switchToRpc = jest.fn().mockReturnValue({
|
|
471
|
+
getContext: jest.fn().mockReturnValue(rmqContext),
|
|
472
|
+
});
|
|
473
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
474
|
+
result.subscribe({
|
|
475
|
+
next: (value) => {
|
|
476
|
+
expect(value).toEqual({
|
|
477
|
+
duplicate: true,
|
|
478
|
+
correlationId,
|
|
479
|
+
});
|
|
480
|
+
done();
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
it("должен обработать случай, когда RmqContext равен null", (done) => {
|
|
485
|
+
const message = {
|
|
486
|
+
correlationId,
|
|
487
|
+
correlationTimestamp,
|
|
488
|
+
data: "test",
|
|
489
|
+
};
|
|
490
|
+
executionContext.getType.mockReturnValue("rpc");
|
|
491
|
+
executionContext.getArgByIndex.mockReturnValue(message);
|
|
492
|
+
redisService.get.mockResolvedValue(String(correlationTimestamp));
|
|
493
|
+
executionContext.switchToRpc = jest.fn().mockReturnValue({
|
|
494
|
+
getContext: jest.fn().mockReturnValue(null),
|
|
495
|
+
});
|
|
496
|
+
const result = interceptor.intercept(executionContext, callHandler);
|
|
497
|
+
result.subscribe({
|
|
498
|
+
next: (value) => {
|
|
499
|
+
expect(value).toEqual({
|
|
500
|
+
duplicate: true,
|
|
501
|
+
correlationId,
|
|
502
|
+
});
|
|
503
|
+
done();
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
describe("constructor", () => {
|
|
509
|
+
it("должен установить правильный контекст логгера", () => {
|
|
510
|
+
expect(mockLoggerInstance.setContext).toHaveBeenCalledWith("RabbitMQIdempotencyInterceptor");
|
|
511
|
+
});
|
|
512
|
+
it("должен вызвать setContext при создании нового экземпляра", () => {
|
|
513
|
+
const testRedisService = {
|
|
514
|
+
get: jest.fn(),
|
|
515
|
+
set: jest.fn(),
|
|
516
|
+
del: jest.fn(),
|
|
517
|
+
};
|
|
518
|
+
const testLoggerInstance = {
|
|
519
|
+
log: jest.fn(),
|
|
520
|
+
error: jest.fn(),
|
|
521
|
+
warn: jest.fn(),
|
|
522
|
+
debug: jest.fn(),
|
|
523
|
+
setContext: jest.fn(),
|
|
524
|
+
};
|
|
525
|
+
LoggerService.mockImplementation(() => testLoggerInstance);
|
|
526
|
+
const testInterceptor = new RabbitMQIdempotencyInterceptor(testRedisService, testLoggerInstance);
|
|
527
|
+
expect(testInterceptor).toBeDefined();
|
|
528
|
+
expect(testLoggerInstance.setContext).toHaveBeenCalledTimes(1);
|
|
529
|
+
expect(testLoggerInstance.setContext).toHaveBeenCalledWith("RabbitMQIdempotencyInterceptor");
|
|
530
|
+
});
|
|
531
|
+
it("должен корректно инициализировать все зависимости конструктора", () => {
|
|
532
|
+
const testRedisService = {
|
|
533
|
+
get: jest.fn(),
|
|
534
|
+
set: jest.fn(),
|
|
535
|
+
del: jest.fn(),
|
|
536
|
+
};
|
|
537
|
+
const testLoggerInstance = {
|
|
538
|
+
log: jest.fn(),
|
|
539
|
+
error: jest.fn(),
|
|
540
|
+
warn: jest.fn(),
|
|
541
|
+
debug: jest.fn(),
|
|
542
|
+
setContext: jest.fn(),
|
|
543
|
+
};
|
|
544
|
+
LoggerService.mockImplementation(() => testLoggerInstance);
|
|
545
|
+
const testInterceptor = new RabbitMQIdempotencyInterceptor(testRedisService, testLoggerInstance);
|
|
546
|
+
expect(testInterceptor).toBeInstanceOf(RabbitMQIdempotencyInterceptor);
|
|
547
|
+
expect(testLoggerInstance.setContext).toHaveBeenCalledWith(RabbitMQIdempotencyInterceptor.name);
|
|
548
|
+
});
|
|
549
|
+
it("должен выполнить тело конструктора (строки 27-28) при создании экземпляра", () => {
|
|
550
|
+
const testRedisService = {
|
|
551
|
+
get: jest.fn(),
|
|
552
|
+
set: jest.fn(),
|
|
553
|
+
del: jest.fn(),
|
|
554
|
+
};
|
|
555
|
+
const testLoggerInstance = {
|
|
556
|
+
log: jest.fn(),
|
|
557
|
+
error: jest.fn(),
|
|
558
|
+
warn: jest.fn(),
|
|
559
|
+
debug: jest.fn(),
|
|
560
|
+
setContext: jest.fn(),
|
|
561
|
+
};
|
|
562
|
+
LoggerService.mockImplementation(() => testLoggerInstance);
|
|
563
|
+
// Создаем новый экземпляр, чтобы гарантировать выполнение строк 27-28
|
|
564
|
+
const testInterceptor = new RabbitMQIdempotencyInterceptor(testRedisService, testLoggerInstance);
|
|
565
|
+
// Проверяем, что конструктор выполнился полностью (строка 27 - открывающая скобка)
|
|
566
|
+
// и что setContext был вызван (строка 28)
|
|
567
|
+
expect(testInterceptor).toBeDefined();
|
|
568
|
+
expect(testInterceptor).toBeInstanceOf(RabbitMQIdempotencyInterceptor);
|
|
569
|
+
expect(testLoggerInstance.setContext).toHaveBeenCalledTimes(1);
|
|
570
|
+
expect(testLoggerInstance.setContext).toHaveBeenCalledWith("RabbitMQIdempotencyInterceptor");
|
|
571
|
+
});
|
|
572
|
+
it("должен выполнить инициализацию logger.setContext в конструкторе (строка 28)", () => {
|
|
573
|
+
const testRedisService = {
|
|
574
|
+
get: jest.fn(),
|
|
575
|
+
set: jest.fn(),
|
|
576
|
+
del: jest.fn(),
|
|
577
|
+
};
|
|
578
|
+
const testLoggerInstance = {
|
|
579
|
+
log: jest.fn(),
|
|
580
|
+
error: jest.fn(),
|
|
581
|
+
warn: jest.fn(),
|
|
582
|
+
debug: jest.fn(),
|
|
583
|
+
setContext: jest.fn(),
|
|
584
|
+
};
|
|
585
|
+
LoggerService.mockImplementation(() => testLoggerInstance);
|
|
586
|
+
// Создаем экземпляр для покрытия строк 27-28
|
|
587
|
+
new RabbitMQIdempotencyInterceptor(testRedisService, testLoggerInstance);
|
|
588
|
+
// Проверяем выполнение строки 28: this.logger.setContext(...)
|
|
589
|
+
expect(testLoggerInstance.setContext).toHaveBeenCalledTimes(1);
|
|
590
|
+
expect(testLoggerInstance.setContext).toHaveBeenCalledWith(RabbitMQIdempotencyInterceptor.name);
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
describe("hasCorrelationId", () => {
|
|
594
|
+
it("должен вернуть true для объекта с корректным correlationId", () => {
|
|
595
|
+
const message = {
|
|
596
|
+
correlationId: "test-id",
|
|
597
|
+
data: "test",
|
|
598
|
+
};
|
|
599
|
+
const result = interceptor.hasCorrelationId(message);
|
|
600
|
+
expect(result).toBe(true);
|
|
601
|
+
});
|
|
602
|
+
it("должен вернуть false для null", () => {
|
|
603
|
+
const result = interceptor.hasCorrelationId(null);
|
|
604
|
+
expect(result).toBe(false);
|
|
605
|
+
});
|
|
606
|
+
it("должен вернуть false для объекта без correlationId", () => {
|
|
607
|
+
const message = {
|
|
608
|
+
data: "test",
|
|
609
|
+
};
|
|
610
|
+
const result = interceptor.hasCorrelationId(message);
|
|
611
|
+
expect(result).toBe(false);
|
|
612
|
+
});
|
|
613
|
+
it("должен вернуть false для объекта с correlationId не строкового типа", () => {
|
|
614
|
+
const message = {
|
|
615
|
+
correlationId: 123,
|
|
616
|
+
};
|
|
617
|
+
const result = interceptor.hasCorrelationId(message);
|
|
618
|
+
expect(result).toBe(false);
|
|
619
|
+
});
|
|
620
|
+
it("должен вернуть false для примитивных типов", () => {
|
|
621
|
+
expect(interceptor.hasCorrelationId("string")).toBe(false);
|
|
622
|
+
expect(interceptor.hasCorrelationId(123)).toBe(false);
|
|
623
|
+
expect(interceptor.hasCorrelationId(true)).toBe(false);
|
|
624
|
+
});
|
|
625
|
+
it("должен вернуть false для undefined", () => {
|
|
626
|
+
const result = interceptor.hasCorrelationId(undefined);
|
|
627
|
+
expect(result).toBe(false);
|
|
628
|
+
});
|
|
629
|
+
it("должен вернуть false для объекта с correlationId равным undefined", () => {
|
|
630
|
+
const message = {
|
|
631
|
+
correlationId: undefined,
|
|
632
|
+
data: "test",
|
|
633
|
+
};
|
|
634
|
+
const result = interceptor.hasCorrelationId(message);
|
|
635
|
+
expect(result).toBe(false);
|
|
636
|
+
});
|
|
637
|
+
it("должен вернуть false для объекта с correlationId равным null", () => {
|
|
638
|
+
const message = {
|
|
639
|
+
correlationId: null,
|
|
640
|
+
data: "test",
|
|
641
|
+
};
|
|
642
|
+
const result = interceptor.hasCorrelationId(message);
|
|
643
|
+
expect(result).toBe(false);
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
//# sourceMappingURL=rabbitmq.interceptor.spec.js.map
|