queen-mq 0.1.6 → 0.2.11
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 +166 -2078
- package/{src → client-js}/benchmark/consumer.js +5 -3
- package/{src → client-js}/benchmark/producer.js +9 -4
- package/client-js/client/client.js +1511 -0
- package/{src → client-js}/client/index.js +1 -4
- package/{src → client-js}/client/utils/http.js +3 -2
- package/{src → client-js}/client/utils/retry.js +7 -1
- package/client-js/test/advanced-client-tests.js +761 -0
- package/{src → client-js}/test/advanced-pattern-tests.js +2 -2
- package/{src → client-js}/test/bus-mode-tests.js +6 -6
- package/{src → client-js}/test/core-tests.js +8 -3
- package/{src → client-js}/test/edge-case-tests.js +4 -3
- package/client-js/test/human.js +162 -0
- package/client-js/test/qos0-tests.js +334 -0
- package/{src → client-js}/test/test-new.js +78 -1
- package/package.json +8 -29
- package/init-db.js +0 -20
- package/src/client/client.js +0 -586
- package/src/cluster-server.js +0 -242
- package/src/config.js +0 -229
- package/src/database/connection.js +0 -129
- package/src/database/poolManager.js +0 -199
- package/src/database/schema-v2.sql +0 -278
- package/src/managers/eventManager.js +0 -59
- package/src/managers/queueManagerOptimized.js +0 -1431
- package/src/managers/resourceCache.js +0 -96
- package/src/managers/systemEventManager.js +0 -132
- package/src/routes/ack.js +0 -26
- package/src/routes/configure.js +0 -46
- package/src/routes/messages.js +0 -368
- package/src/routes/pop.js +0 -69
- package/src/routes/push.js +0 -28
- package/src/routes/resources.js +0 -330
- package/src/routes/status.js +0 -1037
- package/src/server.js +0 -1528
- package/src/test/MIGRATION_ISSUES.md +0 -174
- package/src/test/test.js +0 -4524
- package/src/utils/streaming.js +0 -231
- package/src/webapp-dist/assets/Analytics-DJrE4Rz2.css +0 -1
- package/src/webapp-dist/assets/Analytics-HEMXKmfJ.js +0 -1
- package/src/webapp-dist/assets/ConfirmDialog-YEoCdE7x.js +0 -1
- package/src/webapp-dist/assets/ConsumerGroups-CC5VJywp.css +0 -1
- package/src/webapp-dist/assets/ConsumerGroups-D8Z20HhZ.js +0 -1
- package/src/webapp-dist/assets/Dashboard-BRFOz6it.css +0 -1
- package/src/webapp-dist/assets/Dashboard-Bpx9I32h.js +0 -1
- package/src/webapp-dist/assets/LoadingSpinner-BPttXcbs.js +0 -1
- package/src/webapp-dist/assets/Messages-BOfVHKma.css +0 -1
- package/src/webapp-dist/assets/Messages-EKURCbLl.js +0 -1
- package/src/webapp-dist/assets/QueueDetail-BXXA7xoZ.css +0 -1
- package/src/webapp-dist/assets/QueueDetail-Bmxg0KiA.js +0 -1
- package/src/webapp-dist/assets/Queues-CvrJkrZT.css +0 -1
- package/src/webapp-dist/assets/Queues-yUx9_3-9.js +0 -1
- package/src/webapp-dist/assets/StatusBadge-C1bBtXHh.css +0 -1
- package/src/webapp-dist/assets/StatusBadge-C6N84XmO.js +0 -1
- package/src/webapp-dist/assets/analytics-BapTekLM.js +0 -1
- package/src/webapp-dist/assets/colors-5z6Q_kUr.js +0 -18
- package/src/webapp-dist/assets/index-B7w2bKT6.css +0 -1
- package/src/webapp-dist/assets/index-CUSnwYTH.js +0 -31
- package/src/webapp-dist/assets/messages-CRV6E-xU.js +0 -1
- package/src/webapp-dist/assets/queen-logo-blue.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-cyan.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-indigo.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-orange.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-pink.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-purple.svg +0 -210
- package/src/webapp-dist/assets/queen-logo-rose.svg +0 -239
- package/src/webapp-dist/assets/queen-logo.svg +0 -263
- package/src/webapp-dist/assets/queues-BdUbxfBC.js +0 -1
- package/src/webapp-dist/assets/resources-Bs9U-ceF.js +0 -1
- package/src/webapp-dist/index.html +0 -15
- package/src/websocket/wsServer.js +0 -228
- /package/{src → client-js}/benchmark/consumer_multi.js +0 -0
- /package/{src → client-js}/benchmark/producer_multi.js +0 -0
- /package/{src → client-js}/client/utils/loadBalancer.js +0 -0
- /package/{src → client-js}/services/encryptionService.js +0 -0
- /package/{src → client-js}/services/evictionService.js +0 -0
- /package/{src → client-js}/services/retentionService.js +0 -0
- /package/{src → client-js}/services/startupSync.js +0 -0
- /package/{src → client-js}/test/README.md +0 -0
- /package/{src → client-js}/test/enterprise-tests.js +0 -0
- /package/{src → client-js}/test/partition-locking-tests.js +0 -0
- /package/{src → client-js}/test/utils.js +0 -0
- /package/{src → client-js}/test/window-buffer-test.js +0 -0
- /package/{src → client-js}/utils/logger.js +0 -0
- /package/{src → client-js}/utils/uuid.js +0 -0
|
@@ -401,8 +401,8 @@ export async function testComplexPriorityScenarios(client) {
|
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
// Calculate average priority of first vs last 3 messages
|
|
404
|
-
const firstThree = received.slice(0, 3).map(m => m.
|
|
405
|
-
const lastThree = received.slice(-3).map(m => m.
|
|
404
|
+
const firstThree = received.slice(0, 3).map(m => m.data.priority);
|
|
405
|
+
const lastThree = received.slice(-3).map(m => m.data.priority);
|
|
406
406
|
|
|
407
407
|
const avgFirst = firstThree.reduce((a, b) => a + b, 0) / 3;
|
|
408
408
|
const avgLast = lastThree.reduce((a, b) => a + b, 0) / 3;
|
|
@@ -47,8 +47,8 @@ export async function testBusConsumerGroups(client) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Verify both groups got the same message IDs
|
|
50
|
-
const group1Ids = group1Messages.map(m => m.
|
|
51
|
-
const group2Ids = group2Messages.map(m => m.
|
|
50
|
+
const group1Ids = group1Messages.map(m => m.data.id).sort();
|
|
51
|
+
const group2Ids = group2Messages.map(m => m.data.id).sort();
|
|
52
52
|
|
|
53
53
|
if (JSON.stringify(group1Ids) !== JSON.stringify(group2Ids)) {
|
|
54
54
|
throw new Error('Consumer groups did not receive the same messages');
|
|
@@ -119,8 +119,8 @@ export async function testMixedMode(client) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// Workers should get different messages (competing)
|
|
122
|
-
const worker1Ids = worker1Messages.map(m => m.
|
|
123
|
-
const worker2Ids = worker2Messages.map(m => m.
|
|
122
|
+
const worker1Ids = worker1Messages.map(m => m.data.id);
|
|
123
|
+
const worker2Ids = worker2Messages.map(m => m.data.id);
|
|
124
124
|
const intersection = worker1Ids.filter(id => worker2Ids.includes(id));
|
|
125
125
|
|
|
126
126
|
if (intersection.length > 0) {
|
|
@@ -150,8 +150,8 @@ export async function testMixedMode(client) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
// Verify groups got all messages
|
|
153
|
-
const analyticsIds = analyticsMessages.map(m => m.
|
|
154
|
-
const auditIds = auditMessages.map(m => m.
|
|
153
|
+
const analyticsIds = analyticsMessages.map(m => m.data.id).sort();
|
|
154
|
+
const auditIds = auditMessages.map(m => m.data.id).sort();
|
|
155
155
|
|
|
156
156
|
if (JSON.stringify(analyticsIds) !== JSON.stringify([1, 2, 3, 4, 5, 6])) {
|
|
157
157
|
throw new Error('Analytics group did not receive all messages');
|
|
@@ -113,19 +113,23 @@ export async function testBatchMessagePush(client) {
|
|
|
113
113
|
|
|
114
114
|
// Push batch
|
|
115
115
|
await client.push(`${queue}/batch-partition`, messages);
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
// Verify messages were stored
|
|
118
118
|
const count = await getMessageCount(queue, 'batch-partition');
|
|
119
|
+
|
|
119
120
|
if (count !== batchSize) {
|
|
120
121
|
throw new Error(`Expected ${batchSize} messages in database, got ${count}`);
|
|
121
122
|
}
|
|
122
|
-
|
|
123
|
+
|
|
123
124
|
// Take all messages
|
|
124
125
|
const received = [];
|
|
125
126
|
for await (const msg of client.take(`${queue}/batch-partition`, { limit: batchSize })) {
|
|
126
127
|
received.push(msg);
|
|
128
|
+
|
|
127
129
|
await client.ack(msg);
|
|
128
130
|
}
|
|
131
|
+
|
|
132
|
+
|
|
129
133
|
|
|
130
134
|
if (received.length !== batchSize) {
|
|
131
135
|
throw new Error(`Expected ${batchSize} messages, got ${received.length}`);
|
|
@@ -233,6 +237,7 @@ export async function testDelayedProcessing(client) {
|
|
|
233
237
|
await client.queue(queue, {
|
|
234
238
|
delayedProcessing: 2 // 2 seconds delay
|
|
235
239
|
});
|
|
240
|
+
|
|
236
241
|
|
|
237
242
|
const startTime = Date.now();
|
|
238
243
|
|
|
@@ -418,7 +423,7 @@ export async function testWindowBuffer(client) {
|
|
|
418
423
|
const task = `test-task-${timestamp}`;
|
|
419
424
|
const queue3 = 'test-window-buffer-filtered-' + timestamp;
|
|
420
425
|
|
|
421
|
-
await client.queue(queue3, { windowBuffer: 5 }, ns, task);
|
|
426
|
+
await client.queue(queue3, { windowBuffer: 5 }, { namespace: ns, task: task });
|
|
422
427
|
await client.push(`${queue3}/p1`, { test: 'case3' });
|
|
423
428
|
|
|
424
429
|
let gotCase3Immediate = false;
|
|
@@ -105,10 +105,11 @@ export async function testVeryLargePayloads(client) {
|
|
|
105
105
|
|
|
106
106
|
// Take and verify
|
|
107
107
|
let received = null;
|
|
108
|
-
for await (const msg of client.take(queue, { limit: 1 })) {
|
|
108
|
+
for await (const msg of client.take(queue, { limit: 1, timeout: 20000 })) {
|
|
109
109
|
received = msg.data;
|
|
110
110
|
await client.ack(msg);
|
|
111
111
|
}
|
|
112
|
+
|
|
112
113
|
|
|
113
114
|
if (!received || !received.array || received.array.length !== 10000) {
|
|
114
115
|
throw new Error('Large payload corrupted');
|
|
@@ -487,10 +488,10 @@ export async function testManyConsumerGroups(client) {
|
|
|
487
488
|
}
|
|
488
489
|
|
|
489
490
|
// Verify all groups got the same messages
|
|
490
|
-
const firstGroupIds = groupResults[0].map(m => m.
|
|
491
|
+
const firstGroupIds = groupResults[0].map(m => m.data.id).sort();
|
|
491
492
|
|
|
492
493
|
for (let g = 1; g < numGroups; g++) {
|
|
493
|
-
const groupIds = groupResults[g].map(m => m.
|
|
494
|
+
const groupIds = groupResults[g].map(m => m.data.id).sort();
|
|
494
495
|
if (JSON.stringify(groupIds) !== JSON.stringify(firstGroupIds)) {
|
|
495
496
|
throw new Error(`Group ${g} got different messages`);
|
|
496
497
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test written by an human
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { startTest, passTest, failTest, sleep, log } from './utils.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Test: Pipeline API - Basic Processing
|
|
9
|
+
*/
|
|
10
|
+
export async function testCreateQueue(client) {
|
|
11
|
+
startTest('Create Queue', 'human');
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const queue = 'test-create-queue-hm';
|
|
15
|
+
await client.queue(queue, { leaseTime: 30 });
|
|
16
|
+
passTest('Queue created successfully');
|
|
17
|
+
} catch (error) {
|
|
18
|
+
failTest('Create Queue', error );
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function workerQueueTest(client) {
|
|
23
|
+
startTest('Worker Queue', 'human');
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const queue = 'test-worker-queue-hm';
|
|
27
|
+
const processedQueue = queue + '-processed';
|
|
28
|
+
await client.queueDelete(queue);
|
|
29
|
+
await client.queueDelete(processedQueue);
|
|
30
|
+
await client.queue(queue, { leaseTime: 30 });
|
|
31
|
+
await client.queue(processedQueue, { leaseTime: 30 });
|
|
32
|
+
const pushMessages = async (count) => {
|
|
33
|
+
let messages = [];
|
|
34
|
+
for (let i = 0; i < count; i++) {
|
|
35
|
+
messages.push({ id: i, data: `Message ${i}` });
|
|
36
|
+
}
|
|
37
|
+
await client.push(queue, messages);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
// Test 1
|
|
42
|
+
await pushMessages(10);
|
|
43
|
+
|
|
44
|
+
let counter1 = 0;
|
|
45
|
+
await client
|
|
46
|
+
.pipeline(queue)
|
|
47
|
+
.take(10)
|
|
48
|
+
.process(async (message) => {
|
|
49
|
+
counter1++;
|
|
50
|
+
return { id: message.data.id, data: message.data.data };
|
|
51
|
+
})
|
|
52
|
+
.execute();
|
|
53
|
+
|
|
54
|
+
if (counter1 !== 10) {
|
|
55
|
+
throw new Error('Expected 10 messages to be processed, got ' + counter1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Test 2
|
|
59
|
+
await pushMessages(100);
|
|
60
|
+
|
|
61
|
+
let counter2 = 0;
|
|
62
|
+
await client
|
|
63
|
+
.pipeline(queue)
|
|
64
|
+
.take(10)
|
|
65
|
+
.processBatch(async (messages) => {
|
|
66
|
+
counter2 += messages.length;
|
|
67
|
+
return messages;
|
|
68
|
+
})
|
|
69
|
+
.repeat({ maxIterations: 1, continuous: false })
|
|
70
|
+
.execute();
|
|
71
|
+
|
|
72
|
+
if (counter2 !== 10) {
|
|
73
|
+
throw new Error('Expected 10 messages to be processed, got ' + counter2);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Test 3
|
|
77
|
+
let counter3 = 0;
|
|
78
|
+
await client
|
|
79
|
+
.pipeline(queue)
|
|
80
|
+
.take(10)
|
|
81
|
+
.processBatch(async (messages) => {
|
|
82
|
+
counter3 += messages.length;
|
|
83
|
+
return messages;
|
|
84
|
+
})
|
|
85
|
+
.repeat({ maxIterations: 9, continuous: false })
|
|
86
|
+
.execute();
|
|
87
|
+
|
|
88
|
+
if (counter3 !== 90) {
|
|
89
|
+
throw new Error('Expected 90 messages to be processed, got ' + counter3);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await pushMessages(100);
|
|
93
|
+
|
|
94
|
+
await client
|
|
95
|
+
.pipeline(queue)
|
|
96
|
+
.take(10)
|
|
97
|
+
.processBatch(async (messages) => {
|
|
98
|
+
return messages;
|
|
99
|
+
})
|
|
100
|
+
.atomically((tx, originalMessages, processedMessages) => {
|
|
101
|
+
tx.ack(originalMessages);
|
|
102
|
+
})
|
|
103
|
+
.repeat({ maxIterations: 10, continuous: true })
|
|
104
|
+
.execute();
|
|
105
|
+
|
|
106
|
+
const messagesPopped = await client.takeSingleBatch(queue);
|
|
107
|
+
let counter4 = messagesPopped.length;
|
|
108
|
+
if (counter4 !== 0) {
|
|
109
|
+
throw new Error('Expected 0 message to be processed, got ' + counter4);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Test transaction that works correctly
|
|
113
|
+
await pushMessages(100);
|
|
114
|
+
|
|
115
|
+
await client
|
|
116
|
+
.pipeline(queue)
|
|
117
|
+
.take(10)
|
|
118
|
+
.processBatch(async (messages) => {
|
|
119
|
+
return messages;
|
|
120
|
+
})
|
|
121
|
+
.atomically((tx, originalMessages, processedMessages) => {
|
|
122
|
+
tx.ack(originalMessages);
|
|
123
|
+
tx.push(processedQueue, processedMessages);
|
|
124
|
+
})
|
|
125
|
+
.repeat({ maxIterations: 10, continuous: true })
|
|
126
|
+
.execute();
|
|
127
|
+
|
|
128
|
+
const messagesPopped2 = [];
|
|
129
|
+
for await (const messages of client.takeBatch(processedQueue, { batch: 100 })) {
|
|
130
|
+
messagesPopped2.push(...messages);
|
|
131
|
+
}
|
|
132
|
+
if (messagesPopped2.length !== 100) {
|
|
133
|
+
throw new Error('Expected 100 message to be processed, got ' + counter5);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Test transaction that fails
|
|
137
|
+
|
|
138
|
+
await pushMessages(100);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await client
|
|
142
|
+
.pipeline(queue)
|
|
143
|
+
.take(10)
|
|
144
|
+
.processBatch(async (messages) => {
|
|
145
|
+
return messages;
|
|
146
|
+
})
|
|
147
|
+
.atomically((tx, originalMessages, processedMessages) => {
|
|
148
|
+
originalMessages.map(x => x.leaseId = 'invalid-lease-id-12345');
|
|
149
|
+
tx.ack(originalMessages);
|
|
150
|
+
tx.push(processedQueue, processedMessages);
|
|
151
|
+
})
|
|
152
|
+
.repeat({ maxIterations: 10, continuous: true })
|
|
153
|
+
.execute();
|
|
154
|
+
failTest('Expected transaction to fail, got ' + error);
|
|
155
|
+
} catch (error) {}
|
|
156
|
+
|
|
157
|
+
passTest('Worker Queue test passed');
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(error);
|
|
160
|
+
failTest('Worker Queue test failed', error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { startTest, passTest, failTest } from './utils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test server-side buffering
|
|
5
|
+
* Uses file buffer for fast writes, server flushes every 100ms (configurable via FILE_BUFFER_FLUSH_MS)
|
|
6
|
+
*/
|
|
7
|
+
export async function testQoS0Buffering(client) {
|
|
8
|
+
startTest('Server-side buffering', 'qos0');
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const queue = 'test-qos0-buffer-' + Date.now();
|
|
12
|
+
await client.queue(queue);
|
|
13
|
+
|
|
14
|
+
// Push 10 events with server-side buffering
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
for (let i = 0; i < 10; i++) {
|
|
17
|
+
await client.push(queue, { index: i }, { buffer: true });
|
|
18
|
+
}
|
|
19
|
+
const pushTime = Date.now() - start;
|
|
20
|
+
|
|
21
|
+
// Should return quickly (buffered to file, server-side batching)
|
|
22
|
+
// With file buffering, push should be fast (< 500ms for 10 messages)
|
|
23
|
+
if (pushTime >= 500) {
|
|
24
|
+
throw new Error(`Push should be fast (file buffered), took ${pushTime}ms`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Wait for server to flush buffer (FILE_BUFFER_FLUSH_MS default is 100ms)
|
|
28
|
+
// Add extra margin for processing
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
30
|
+
|
|
31
|
+
// Consume all events
|
|
32
|
+
const messages = [];
|
|
33
|
+
for await (const msg of client.take(queue, { batch: 20 })) {
|
|
34
|
+
messages.push(msg);
|
|
35
|
+
await client.ack(msg);
|
|
36
|
+
if (messages.length >= 10) break;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (messages.length !== 10) {
|
|
40
|
+
throw new Error(`Should receive all 10 events, got ${messages.length}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Verify all events received
|
|
44
|
+
const indices = messages.map(m => m.data.index).sort((a, b) => a - b);
|
|
45
|
+
for (let i = 0; i < 10; i++) {
|
|
46
|
+
if (!indices.includes(i)) {
|
|
47
|
+
throw new Error(`Should have event with index ${i}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
passTest('Server-side buffering works correctly');
|
|
52
|
+
} catch (error) {
|
|
53
|
+
failTest(error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Test auto-acknowledgment
|
|
59
|
+
*/
|
|
60
|
+
export async function testAutoAck(client) {
|
|
61
|
+
startTest('Auto-acknowledgment', 'qos0');
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const queue = 'test-autoack-' + Date.now();
|
|
65
|
+
await client.queue(queue);
|
|
66
|
+
|
|
67
|
+
// Push message
|
|
68
|
+
await client.push(queue, { test: 'autoack-data' });
|
|
69
|
+
|
|
70
|
+
// Take with auto-ack
|
|
71
|
+
let msg;
|
|
72
|
+
for await (const m of client.take(queue, { autoAck: true })) {
|
|
73
|
+
msg = m;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (msg === undefined) {
|
|
78
|
+
throw new Error('Should receive message');
|
|
79
|
+
}
|
|
80
|
+
if (msg.data.test !== 'autoack-data') {
|
|
81
|
+
throw new Error('Should have correct data');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Message should NOT be available again (already auto-acked)
|
|
85
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
86
|
+
|
|
87
|
+
let count = 0;
|
|
88
|
+
for await (const m of client.take(queue, { wait: false, batch: 10 })) {
|
|
89
|
+
count++;
|
|
90
|
+
await client.ack(m);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (count !== 0) {
|
|
95
|
+
throw new Error('Message should not be available again (auto-acked)');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
passTest('Auto-ack works correctly');
|
|
99
|
+
} catch (error) {
|
|
100
|
+
failTest(error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Test consumer group with auto-ack
|
|
106
|
+
*/
|
|
107
|
+
export async function testConsumerGroupAutoAck(client) {
|
|
108
|
+
startTest('Consumer group with auto-ack', 'qos0');
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const queue = 'test-cg-autoack-' + Date.now();
|
|
112
|
+
await client.queue(queue);
|
|
113
|
+
|
|
114
|
+
// Push message
|
|
115
|
+
await client.push(queue, { value: 42, type: 'test' });
|
|
116
|
+
|
|
117
|
+
// Wait a bit for message to be available
|
|
118
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
119
|
+
|
|
120
|
+
// Two consumer groups consume same message
|
|
121
|
+
let msgA, msgB;
|
|
122
|
+
|
|
123
|
+
for await (const msg of client.take(`${queue}@groupA`, { autoAck: true })) {
|
|
124
|
+
msgA = msg;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for await (const msg of client.take(`${queue}@groupB`, { autoAck: true })) {
|
|
129
|
+
msgB = msg;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (msgA === undefined) {
|
|
134
|
+
throw new Error('Group A should receive message');
|
|
135
|
+
}
|
|
136
|
+
if (msgB === undefined) {
|
|
137
|
+
throw new Error('Group B should receive message');
|
|
138
|
+
}
|
|
139
|
+
if (msgA.data.value !== 42) {
|
|
140
|
+
throw new Error('Group A should have correct data');
|
|
141
|
+
}
|
|
142
|
+
if (msgB.data.value !== 42) {
|
|
143
|
+
throw new Error('Group B should have correct data');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Both groups should have auto-acked - message not available again
|
|
147
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
148
|
+
|
|
149
|
+
let countA = 0, countB = 0;
|
|
150
|
+
|
|
151
|
+
for await (const msg of client.take(`${queue}@groupA`, { wait: false })) {
|
|
152
|
+
countA++;
|
|
153
|
+
await client.ack(msg, true, { group: 'groupA' });
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for await (const msg of client.take(`${queue}@groupB`, { wait: false })) {
|
|
158
|
+
countB++;
|
|
159
|
+
await client.ack(msg, true, { group: 'groupB' });
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (countA !== 0) {
|
|
164
|
+
throw new Error('Group A should have auto-acked');
|
|
165
|
+
}
|
|
166
|
+
if (countB !== 0) {
|
|
167
|
+
throw new Error('Group B should have auto-acked');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
passTest('Consumer group auto-ack works correctly');
|
|
171
|
+
} catch (error) {
|
|
172
|
+
failTest(error);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Test normal push preserves FIFO ordering
|
|
178
|
+
*/
|
|
179
|
+
export async function testFIFOOrdering(client) {
|
|
180
|
+
startTest('Normal push preserves FIFO', 'qos0');
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const queue = 'test-fifo-' + Date.now();
|
|
184
|
+
await client.queue(queue);
|
|
185
|
+
|
|
186
|
+
// Push without buffering (normal mode)
|
|
187
|
+
for (let i = 0; i < 20; i++) {
|
|
188
|
+
await client.push(queue, { order: i });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Consume and verify order
|
|
192
|
+
const messages = [];
|
|
193
|
+
for await (const msg of client.take(queue, { batch: 20 })) {
|
|
194
|
+
messages.push(msg);
|
|
195
|
+
await client.ack(msg);
|
|
196
|
+
if (messages.length >= 20) break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (messages.length !== 20) {
|
|
200
|
+
throw new Error(`Should have 20 messages, got ${messages.length}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Verify FIFO ordering
|
|
204
|
+
for (let i = 0; i < 20; i++) {
|
|
205
|
+
if (messages[i].data.order !== i) {
|
|
206
|
+
throw new Error(`Message ${i} out of order: got ${messages[i].data.order}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
passTest('FIFO ordering preserved for normal push');
|
|
211
|
+
} catch (error) {
|
|
212
|
+
failTest(error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Test mixed buffered and non-buffered operations
|
|
218
|
+
*/
|
|
219
|
+
export async function testMixedOperations(client) {
|
|
220
|
+
startTest('Mixed buffered and normal operations', 'qos0');
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const queue = 'test-mixed-' + Date.now();
|
|
224
|
+
await client.queue(queue);
|
|
225
|
+
|
|
226
|
+
// Push with buffering (file buffered)
|
|
227
|
+
await client.push(queue, { type: 'buffered', id: 1 }, { buffer: true });
|
|
228
|
+
|
|
229
|
+
// Push normal (direct DB)
|
|
230
|
+
await client.push(queue, { type: 'normal', id: 2 });
|
|
231
|
+
|
|
232
|
+
// Wait for buffer flush (100ms + margin)
|
|
233
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
234
|
+
|
|
235
|
+
// Should have both messages
|
|
236
|
+
const messages = [];
|
|
237
|
+
for await (const msg of client.take(queue, { batch: 10 })) {
|
|
238
|
+
messages.push(msg);
|
|
239
|
+
await client.ack(msg);
|
|
240
|
+
if (messages.length >= 2) break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (messages.length !== 2) {
|
|
244
|
+
throw new Error(`Should have 2 messages, got ${messages.length}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const types = messages.map(m => m.data.type);
|
|
248
|
+
if (!types.includes('buffered')) {
|
|
249
|
+
throw new Error('Should have buffered message');
|
|
250
|
+
}
|
|
251
|
+
if (!types.includes('normal')) {
|
|
252
|
+
throw new Error('Should have normal message');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
passTest('Mixed buffered and normal operations work correctly');
|
|
256
|
+
} catch (error) {
|
|
257
|
+
failTest(error);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Test buffered events are stored individually
|
|
263
|
+
* Note: Buffering is a server-side optimization for DB writes,
|
|
264
|
+
* not for combining events into a single message
|
|
265
|
+
*/
|
|
266
|
+
export async function testBatchedPayload(client) {
|
|
267
|
+
startTest('Buffered events stored individually', 'qos0');
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const queue = 'test-batch-payload-' + Date.now();
|
|
271
|
+
await client.queue(queue);
|
|
272
|
+
|
|
273
|
+
// Push multiple events with buffering
|
|
274
|
+
for (let i = 0; i < 5; i++) {
|
|
275
|
+
await client.push(queue, { index: i, timestamp: Date.now() }, { buffer: true });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Wait for server flush (100ms + margin)
|
|
279
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
280
|
+
|
|
281
|
+
// Consume all messages - each event should be a separate message
|
|
282
|
+
const messages = [];
|
|
283
|
+
for await (const msg of client.take(queue, { batch: 10 })) {
|
|
284
|
+
messages.push(msg);
|
|
285
|
+
await client.ack(msg);
|
|
286
|
+
if (messages.length >= 5) break;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (messages.length !== 5) {
|
|
290
|
+
throw new Error(`Should receive 5 separate messages, got ${messages.length}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Verify each message has individual payload (not batched into array)
|
|
294
|
+
for (const msg of messages) {
|
|
295
|
+
if (typeof msg.data !== 'object' || Array.isArray(msg.data)) {
|
|
296
|
+
throw new Error(`Each message should have object payload, got ${typeof msg.data}`);
|
|
297
|
+
}
|
|
298
|
+
if (typeof msg.data.index !== 'number') {
|
|
299
|
+
throw new Error('Each message should have index property');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Verify all indices are present
|
|
304
|
+
const indices = messages.map(m => m.data.index).sort((a, b) => a - b);
|
|
305
|
+
for (let i = 0; i < 5; i++) {
|
|
306
|
+
if (!indices.includes(i)) {
|
|
307
|
+
throw new Error(`Should have message with index ${i}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
passTest('Buffered events stored and retrieved individually');
|
|
312
|
+
} catch (error) {
|
|
313
|
+
failTest(error);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Run all QoS 0 tests
|
|
319
|
+
*/
|
|
320
|
+
export async function runQoS0Tests(client) {
|
|
321
|
+
console.log('\n╔════════════════════════════════════════╗');
|
|
322
|
+
console.log('║ Queen QoS 0 Test Suite ║');
|
|
323
|
+
console.log('╚════════════════════════════════════════╝\n');
|
|
324
|
+
|
|
325
|
+
await testQoS0Buffering(client);
|
|
326
|
+
await testAutoAck(client);
|
|
327
|
+
await testConsumerGroupAutoAck(client);
|
|
328
|
+
await testFIFOOrdering(client);
|
|
329
|
+
await testMixedOperations(client);
|
|
330
|
+
await testBatchedPayload(client);
|
|
331
|
+
|
|
332
|
+
console.log('\n✅ All QoS 0 tests passed!\n');
|
|
333
|
+
}
|
|
334
|
+
|