queen-mq 0.3.0 → 0.4.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 +212 -144
- package/client-js/client-v2/Queen.js +28 -0
- package/client-js/client-v2/README.md +6 -2
- package/client-js/client-v2/builders/QueueBuilder.js +7 -1
- package/client-js/client-v2/builders/TransactionBuilder.js +20 -4
- package/client-js/client-v2/stream/StreamBuilder.js +72 -0
- package/client-js/client-v2/stream/StreamConsumer.js +156 -0
- package/client-js/client-v2/stream/Window.js +140 -0
- package/client-js/test-v2/MAINTENANCE_TEST.md +148 -0
- package/client-js/test-v2/consume.js +1 -0
- package/client-js/test-v2/maintenance.js +261 -0
- package/client-js/test-v2/retention.js +69 -0
- package/client-js/test-v2/run.js +7 -1
- package/package.json +2 -2
- package/client-js/client/client.js +0 -1536
- package/client-js/client/index.js +0 -4
- package/client-js/client/utils/http.js +0 -173
- package/client-js/client/utils/loadBalancer.js +0 -152
- package/client-js/client/utils/retry.js +0 -41
- package/client-js/services/encryptionService.js +0 -82
- package/client-js/services/evictionService.js +0 -160
- package/client-js/services/retentionService.js +0 -162
- package/client-js/services/startupSync.js +0 -35
- package/client-js/test/README.md +0 -224
- package/client-js/test/advanced-client-tests.js +0 -761
- package/client-js/test/advanced-pattern-tests.js +0 -1137
- package/client-js/test/bus-mode-tests.js +0 -361
- package/client-js/test/core-tests.js +0 -457
- package/client-js/test/edge-case-tests.js +0 -562
- package/client-js/test/enterprise-tests.js +0 -637
- package/client-js/test/human.js +0 -162
- package/client-js/test/partition-locking-tests.js +0 -545
- package/client-js/test/partition-transaction-tests.js +0 -482
- package/client-js/test/qos0-tests.js +0 -334
- package/client-js/test/test-new.js +0 -370
- package/client-js/test/utils.js +0 -169
- package/client-js/test/window-buffer-test.js +0 -114
- package/client-js/utils/logger.js +0 -44
- package/client-js/utils/uuid.js +0 -5
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bus Mode Tests for Queen Message Queue
|
|
3
|
-
* Tests: Consumer groups, mixed mode, subscription modes, consumer group isolation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { startTest, passTest, failTest, sleep, dbPool, log } from './utils.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Test: Bus Mode - Consumer Groups
|
|
10
|
-
*/
|
|
11
|
-
export async function testBusConsumerGroups(client) {
|
|
12
|
-
startTest('Bus Mode - Consumer Groups', 'enterprise');
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const queue = 'test-bus-mode';
|
|
16
|
-
|
|
17
|
-
// Configure queue
|
|
18
|
-
await client.queue(queue, {});
|
|
19
|
-
|
|
20
|
-
// Push test messages
|
|
21
|
-
const messages = Array.from({ length: 5 }, (_, i) => ({
|
|
22
|
-
id: i + 1,
|
|
23
|
-
data: `Message ${i + 1}`
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
await client.push(queue, messages);
|
|
27
|
-
await sleep(100);
|
|
28
|
-
|
|
29
|
-
// Consumer Group 1: Take messages
|
|
30
|
-
const group1Messages = [];
|
|
31
|
-
for await (const msg of client.take(`${queue}@group1`, { limit: 3, batch: 3 })) {
|
|
32
|
-
group1Messages.push(msg);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (group1Messages.length !== 3) {
|
|
36
|
-
throw new Error(`Group1 expected 3 messages, got ${group1Messages.length}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Consumer Group 2: Should get the same messages
|
|
40
|
-
const group2Messages = [];
|
|
41
|
-
for await (const msg of client.take(`${queue}@group2`, { limit: 3, batch: 3 })) {
|
|
42
|
-
group2Messages.push(msg);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (group2Messages.length !== 3) {
|
|
46
|
-
throw new Error(`Group2 expected 3 messages, got ${group2Messages.length}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Verify both groups got the same message IDs
|
|
50
|
-
const group1Ids = group1Messages.map(m => m.data.id).sort();
|
|
51
|
-
const group2Ids = group2Messages.map(m => m.data.id).sort();
|
|
52
|
-
|
|
53
|
-
if (JSON.stringify(group1Ids) !== JSON.stringify(group2Ids)) {
|
|
54
|
-
throw new Error('Consumer groups did not receive the same messages');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Acknowledge messages for both groups
|
|
58
|
-
for (const msg of group1Messages) {
|
|
59
|
-
await client.ack(msg, true, { group: 'group1' });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for (const msg of group2Messages) {
|
|
63
|
-
await client.ack(msg, true, { group: 'group2' });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Take remaining messages
|
|
67
|
-
const group1Remaining = [];
|
|
68
|
-
for await (const msg of client.take(`${queue}@group1`, { limit: 5 })) {
|
|
69
|
-
group1Remaining.push(msg);
|
|
70
|
-
await client.ack(msg, true, { group: 'group1' });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (group1Remaining.length !== 2) {
|
|
74
|
-
throw new Error(`Expected 2 remaining messages for group1, got ${group1Remaining.length}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
passTest('Consumer groups receive all messages independently');
|
|
78
|
-
} catch (error) {
|
|
79
|
-
failTest(error);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Test: Mixed Mode - Queue and Bus Together
|
|
85
|
-
*/
|
|
86
|
-
export async function testMixedMode(client) {
|
|
87
|
-
startTest('Mixed Mode - Queue and Bus Together', 'enterprise');
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const queue = 'test-mixed-mode';
|
|
91
|
-
|
|
92
|
-
// Configure queue
|
|
93
|
-
await client.queue(queue, {});
|
|
94
|
-
|
|
95
|
-
// Push test messages
|
|
96
|
-
const messages = Array.from({ length: 6 }, (_, i) => ({
|
|
97
|
-
id: i + 1,
|
|
98
|
-
data: `Mixed message ${i + 1}`
|
|
99
|
-
}));
|
|
100
|
-
|
|
101
|
-
await client.push(queue, messages);
|
|
102
|
-
await sleep(100);
|
|
103
|
-
|
|
104
|
-
// Queue Mode: Workers compete for messages
|
|
105
|
-
const worker1Messages = [];
|
|
106
|
-
for await (const msg of client.take(queue, { limit: 2, batch: 2 })) {
|
|
107
|
-
worker1Messages.push(msg);
|
|
108
|
-
await client.ack(msg);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const worker2Messages = [];
|
|
112
|
-
for await (const msg of client.take(queue, { limit: 2, batch: 2 })) {
|
|
113
|
-
worker2Messages.push(msg);
|
|
114
|
-
await client.ack(msg);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!worker1Messages.length || !worker2Messages.length) {
|
|
118
|
-
throw new Error('Workers did not receive messages');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Workers should get different messages (competing)
|
|
122
|
-
const worker1Ids = worker1Messages.map(m => m.data.id);
|
|
123
|
-
const worker2Ids = worker2Messages.map(m => m.data.id);
|
|
124
|
-
const intersection = worker1Ids.filter(id => worker2Ids.includes(id));
|
|
125
|
-
|
|
126
|
-
if (intersection.length > 0) {
|
|
127
|
-
throw new Error('Workers received the same messages - should be competing');
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Bus Mode: Consumer groups get all messages
|
|
131
|
-
const analyticsMessages = [];
|
|
132
|
-
for await (const msg of client.take(`${queue}@analytics`, { limit: 10 })) {
|
|
133
|
-
analyticsMessages.push(msg);
|
|
134
|
-
await client.ack(msg, true, { group: 'analytics' });
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const auditMessages = [];
|
|
138
|
-
for await (const msg of client.take(`${queue}@audit`, { limit: 10 })) {
|
|
139
|
-
auditMessages.push(msg);
|
|
140
|
-
await client.ack(msg, true, { group: 'audit' });
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Both groups should get all 6 messages
|
|
144
|
-
if (analyticsMessages.length !== 6) {
|
|
145
|
-
throw new Error(`Analytics group expected 6 messages, got ${analyticsMessages.length}`);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (auditMessages.length !== 6) {
|
|
149
|
-
throw new Error(`Audit group expected 6 messages, got ${auditMessages.length}`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Verify groups got all messages
|
|
153
|
-
const analyticsIds = analyticsMessages.map(m => m.data.id).sort();
|
|
154
|
-
const auditIds = auditMessages.map(m => m.data.id).sort();
|
|
155
|
-
|
|
156
|
-
if (JSON.stringify(analyticsIds) !== JSON.stringify([1, 2, 3, 4, 5, 6])) {
|
|
157
|
-
throw new Error('Analytics group did not receive all messages');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (JSON.stringify(auditIds) !== JSON.stringify([1, 2, 3, 4, 5, 6])) {
|
|
161
|
-
throw new Error('Audit group did not receive all messages');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
passTest('Mixed mode works: workers compete, groups get all messages');
|
|
165
|
-
} catch (error) {
|
|
166
|
-
failTest(error);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Test: Consumer Group Subscription Modes
|
|
172
|
-
*/
|
|
173
|
-
export async function testConsumerGroupSubscriptionModes(client) {
|
|
174
|
-
startTest('Consumer Group Subscription Modes', 'enterprise');
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const queue = 'test-subscription-modes';
|
|
178
|
-
|
|
179
|
-
// Configure queue
|
|
180
|
-
await client.queue(queue, {});
|
|
181
|
-
|
|
182
|
-
// Push historical messages
|
|
183
|
-
const historicalMessages = Array.from({ length: 3 }, (_, i) => ({
|
|
184
|
-
id: i + 1,
|
|
185
|
-
type: 'historical',
|
|
186
|
-
data: `Historical ${i + 1}`
|
|
187
|
-
}));
|
|
188
|
-
|
|
189
|
-
await client.push(queue, historicalMessages);
|
|
190
|
-
await sleep(100);
|
|
191
|
-
|
|
192
|
-
// Group 1: Default mode (consume all)
|
|
193
|
-
const allMessages = [];
|
|
194
|
-
for await (const msg of client.take(`${queue}@all-messages`, { limit: 10 })) {
|
|
195
|
-
allMessages.push(msg);
|
|
196
|
-
// ACK immediately to release partition lock
|
|
197
|
-
await client.ack(msg, true, { group: 'all-messages' });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (allMessages.length !== 3) {
|
|
201
|
-
throw new Error(`All-messages group expected 3 messages, got ${allMessages.length}`);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Group 2: New messages only (subscription mode = 'new')
|
|
205
|
-
const newOnlyMessages = [];
|
|
206
|
-
for await (const msg of client.take(`${queue}@new-only`, {
|
|
207
|
-
limit: 10,
|
|
208
|
-
subscriptionMode: 'new'
|
|
209
|
-
})) {
|
|
210
|
-
newOnlyMessages.push(msg);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (newOnlyMessages.length !== 0) {
|
|
214
|
-
throw new Error(`New-only group should get 0 historical messages, got ${newOnlyMessages.length}`);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Wait to ensure subscription timestamp is in the past
|
|
218
|
-
await sleep(1000);
|
|
219
|
-
|
|
220
|
-
// Push new messages
|
|
221
|
-
const newMessages = Array.from({ length: 3 }, (_, i) => ({
|
|
222
|
-
id: i + 4,
|
|
223
|
-
type: 'new',
|
|
224
|
-
data: `New ${i + 4}`
|
|
225
|
-
}));
|
|
226
|
-
|
|
227
|
-
await client.push(queue, newMessages);
|
|
228
|
-
await sleep(100);
|
|
229
|
-
|
|
230
|
-
// All-messages group should get the new messages
|
|
231
|
-
const allMessagesNew = [];
|
|
232
|
-
for await (const msg of client.take(`${queue}@all-messages`, { limit: 10 })) {
|
|
233
|
-
allMessagesNew.push(msg);
|
|
234
|
-
// ACK immediately
|
|
235
|
-
await client.ack(msg, true, { group: 'all-messages' });
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (allMessagesNew.length !== 3) {
|
|
239
|
-
throw new Error(`All-messages group expected 3 new messages, got ${allMessagesNew.length}`);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// New-only group should also get the new messages
|
|
243
|
-
const newOnlyNew = [];
|
|
244
|
-
for await (const msg of client.take(`${queue}@new-only`, { limit: 10 })) {
|
|
245
|
-
newOnlyNew.push(msg);
|
|
246
|
-
await client.ack(msg, true, { group: 'new-only' });
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (newOnlyNew.length !== 3) {
|
|
250
|
-
throw new Error(`New-only group expected 3 new messages, got ${newOnlyNew.length}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
passTest('Consumer group subscription modes work correctly');
|
|
254
|
-
} catch (error) {
|
|
255
|
-
failTest(error);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Test: Consumer Group Isolation
|
|
261
|
-
*/
|
|
262
|
-
export async function testConsumerGroupIsolation(client) {
|
|
263
|
-
startTest('Consumer Group Isolation', 'enterprise');
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
const queue = 'test-group-isolation';
|
|
267
|
-
|
|
268
|
-
// Configure queue
|
|
269
|
-
await client.queue(queue, {});
|
|
270
|
-
|
|
271
|
-
// Push messages
|
|
272
|
-
const messages = Array.from({ length: 4 }, (_, i) => ({
|
|
273
|
-
id: i + 1,
|
|
274
|
-
data: `Isolation test ${i + 1}`
|
|
275
|
-
}));
|
|
276
|
-
|
|
277
|
-
await client.push(queue, messages);
|
|
278
|
-
await sleep(100);
|
|
279
|
-
|
|
280
|
-
// Group 1: Take some messages first
|
|
281
|
-
const group1Messages = [];
|
|
282
|
-
for await (const msg of client.take(`${queue}@group1`, { limit: 2, batch: 2 })) {
|
|
283
|
-
group1Messages.push(msg);
|
|
284
|
-
await client.ack(msg, true, { group: 'group1' });
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (group1Messages.length !== 2) {
|
|
288
|
-
throw new Error(`Group1 expected 2 messages, got ${group1Messages.length}`);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Group 2: Should get ALL 4 messages (different consumer group, independent)
|
|
292
|
-
// Consumer groups don't race - they each get all messages
|
|
293
|
-
const group2Messages = [];
|
|
294
|
-
for await (const msg of client.take(`${queue}@group2`, { limit: 10, batch: 10 })) {
|
|
295
|
-
group2Messages.push(msg);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (group2Messages.length !== 4) {
|
|
299
|
-
log(`Group2 got ${group2Messages.length} messages, expected 4 - consumer group isolation may not be working`, 'warning');
|
|
300
|
-
throw new Error(`Group2 expected all 4 messages, got ${group2Messages.length}`);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Group 1: Take remaining messages
|
|
304
|
-
const group1Remaining = [];
|
|
305
|
-
for await (const msg of client.take(`${queue}@group1`, { limit: 10, batch: 10 })) {
|
|
306
|
-
group1Remaining.push(msg);
|
|
307
|
-
await client.ack(msg, true, { group: 'group1' });
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (group1Remaining.length !== 2) {
|
|
311
|
-
throw new Error(`Group1 expected 2 remaining messages, got ${group1Remaining.length}`);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Verify isolation: Fail first 2 messages for group2, complete the rest
|
|
315
|
-
for (let i = 0; i < group2Messages.length; i++) {
|
|
316
|
-
if (i < 2) {
|
|
317
|
-
await client.ack(group2Messages[i], false, { error: 'Test failure', group: 'group2' });
|
|
318
|
-
} else {
|
|
319
|
-
await client.ack(group2Messages[i], true, { group: 'group2' });
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Verify isolation: Check that group1's first message is completed (cursor advanced) and group2 failed (in DLQ)
|
|
324
|
-
const statusCheck = await dbPool.query(`
|
|
325
|
-
SELECT
|
|
326
|
-
m.id as message_id,
|
|
327
|
-
m.transaction_id,
|
|
328
|
-
CASE
|
|
329
|
-
WHEN dlq1.id IS NOT NULL THEN 'failed'
|
|
330
|
-
WHEN m.id <= COALESCE(pc1.last_consumed_id, '00000000-0000-0000-0000-000000000000'::uuid) THEN 'completed'
|
|
331
|
-
ELSE 'pending'
|
|
332
|
-
END as group1_status,
|
|
333
|
-
CASE
|
|
334
|
-
WHEN dlq2.id IS NOT NULL THEN 'failed'
|
|
335
|
-
WHEN m.id <= COALESCE(pc2.last_consumed_id, '00000000-0000-0000-0000-000000000000'::uuid) THEN 'completed'
|
|
336
|
-
ELSE 'pending'
|
|
337
|
-
END as group2_status
|
|
338
|
-
FROM queen.messages m
|
|
339
|
-
LEFT JOIN queen.partition_consumers pc1 ON pc1.partition_id = m.partition_id AND pc1.consumer_group = 'group1'
|
|
340
|
-
LEFT JOIN queen.partition_consumers pc2 ON pc2.partition_id = m.partition_id AND pc2.consumer_group = 'group2'
|
|
341
|
-
LEFT JOIN queen.dead_letter_queue dlq1 ON dlq1.message_id = m.id AND dlq1.consumer_group = 'group1'
|
|
342
|
-
LEFT JOIN queen.dead_letter_queue dlq2 ON dlq2.message_id = m.id AND dlq2.consumer_group = 'group2'
|
|
343
|
-
WHERE m.transaction_id = $1
|
|
344
|
-
`, [group1Messages[0].transactionId]);
|
|
345
|
-
|
|
346
|
-
const result = statusCheck.rows[0];
|
|
347
|
-
|
|
348
|
-
if (result?.group1_status !== 'completed') {
|
|
349
|
-
throw new Error(`Group1 status should be completed, got ${result?.group1_status}`);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (result?.group2_status !== 'failed') {
|
|
353
|
-
throw new Error(`Group2 status should be failed, got ${result?.group2_status}`);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
passTest('Consumer groups are properly isolated');
|
|
357
|
-
} catch (error) {
|
|
358
|
-
failTest(error);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|