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,457 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core Feature Tests for Queen Message Queue
|
|
3
|
-
* Tests: Queue configuration, push, take, acknowledgment, delayed processing, FIFO ordering
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { startTest, passTest, failTest, sleep, dbPool, getMessageCount, log } from './utils.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Test 1: Queue Creation Policy
|
|
10
|
-
* Verifies that queues must be created via queue() method
|
|
11
|
-
*/
|
|
12
|
-
export async function testQueueCreationPolicy(client) {
|
|
13
|
-
startTest('Queue Creation Policy');
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
// Test 1: Push to non-existent queue should fail
|
|
17
|
-
const nonExistentQueue = 'queue-that-does-not-exist-' + Date.now();
|
|
18
|
-
try {
|
|
19
|
-
await client.push(nonExistentQueue, { test: 'should fail' });
|
|
20
|
-
throw new Error('Push to non-existent queue should have failed');
|
|
21
|
-
} catch (error) {
|
|
22
|
-
if (!error.message.includes('does not exist')) {
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
log('Push to non-existent queue correctly failed', 'success');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Test 2: Create queue via queue() method
|
|
29
|
-
const testQueue = 'test-creation-policy-' + Date.now();
|
|
30
|
-
await client.queue(testQueue, {
|
|
31
|
-
retryLimit: 3,
|
|
32
|
-
priority: 5
|
|
33
|
-
}, {
|
|
34
|
-
namespace: 'test',
|
|
35
|
-
task: 'creation-policy'
|
|
36
|
-
});
|
|
37
|
-
log('Queue created via queue() method', 'success');
|
|
38
|
-
|
|
39
|
-
// Test 3: Push to configured queue should succeed
|
|
40
|
-
await client.push(testQueue, { test: 'message 1' });
|
|
41
|
-
log('Push to configured queue succeeded', 'success');
|
|
42
|
-
|
|
43
|
-
// Test 4: Push to new partition (should create on-demand)
|
|
44
|
-
await client.push(`${testQueue}/NewPartition`, { test: 'message 2' });
|
|
45
|
-
log('Partition created on-demand', 'success');
|
|
46
|
-
|
|
47
|
-
passTest('Queue creation policy working correctly');
|
|
48
|
-
} catch (error) {
|
|
49
|
-
failTest(error);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Test 2: Single Message Push
|
|
55
|
-
*/
|
|
56
|
-
export async function testSingleMessagePush(client) {
|
|
57
|
-
startTest('Single Message Push');
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const queue = 'test-single-push';
|
|
61
|
-
|
|
62
|
-
// Configure queue first
|
|
63
|
-
await client.queue(queue, {});
|
|
64
|
-
|
|
65
|
-
// Push single message
|
|
66
|
-
await client.push(queue, {
|
|
67
|
-
message: 'Single test message',
|
|
68
|
-
timestamp: Date.now()
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Verify message was stored
|
|
72
|
-
const count = await getMessageCount(queue);
|
|
73
|
-
if (count !== 1) {
|
|
74
|
-
throw new Error(`Expected 1 message in database, got ${count}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Take and verify the message
|
|
78
|
-
let messageReceived = false;
|
|
79
|
-
for await (const msg of client.take(queue, { limit: 1 })) {
|
|
80
|
-
if (msg.data && msg.data.message === 'Single test message') {
|
|
81
|
-
messageReceived = true;
|
|
82
|
-
await client.ack(msg);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!messageReceived) {
|
|
87
|
-
throw new Error('Failed to receive pushed message');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
passTest('Single message pushed successfully');
|
|
91
|
-
} catch (error) {
|
|
92
|
-
failTest(error);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Test 3: Batch Message Push
|
|
98
|
-
*/
|
|
99
|
-
export async function testBatchMessagePush(client) {
|
|
100
|
-
startTest('Batch Message Push');
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const queue = 'test-batch-push';
|
|
104
|
-
|
|
105
|
-
// Configure queue first
|
|
106
|
-
await client.queue(queue, {});
|
|
107
|
-
|
|
108
|
-
const batchSize = 5;
|
|
109
|
-
const messages = Array.from({ length: batchSize }, (_, i) => ({
|
|
110
|
-
message: `Batch message ${i + 1}`,
|
|
111
|
-
index: i + 1
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
// Push batch
|
|
115
|
-
await client.push(`${queue}/batch-partition`, messages);
|
|
116
|
-
|
|
117
|
-
// Verify messages were stored
|
|
118
|
-
const count = await getMessageCount(queue, 'batch-partition');
|
|
119
|
-
|
|
120
|
-
if (count !== batchSize) {
|
|
121
|
-
throw new Error(`Expected ${batchSize} messages in database, got ${count}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Take all messages
|
|
125
|
-
const received = [];
|
|
126
|
-
for await (const msg of client.take(`${queue}/batch-partition`, { limit: batchSize })) {
|
|
127
|
-
received.push(msg);
|
|
128
|
-
|
|
129
|
-
await client.ack(msg);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (received.length !== batchSize) {
|
|
135
|
-
throw new Error(`Expected ${batchSize} messages, got ${received.length}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
passTest(`Batch of ${batchSize} messages pushed successfully`);
|
|
139
|
-
} catch (error) {
|
|
140
|
-
failTest(error);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Test 4: Queue Configuration
|
|
146
|
-
*/
|
|
147
|
-
export async function testQueueConfiguration(client) {
|
|
148
|
-
startTest('Queue Configuration');
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const queue = 'test-config-queue';
|
|
152
|
-
|
|
153
|
-
await client.queue(queue, {
|
|
154
|
-
leaseTime: 600,
|
|
155
|
-
retryLimit: 5,
|
|
156
|
-
priority: 8,
|
|
157
|
-
delayedProcessing: 2,
|
|
158
|
-
windowBuffer: 3
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Verify configuration was applied by checking database
|
|
162
|
-
const result = await dbPool.query(`
|
|
163
|
-
SELECT lease_time, retry_limit, priority, delayed_processing, window_buffer
|
|
164
|
-
FROM queen.queues
|
|
165
|
-
WHERE name = $1
|
|
166
|
-
`, [queue]);
|
|
167
|
-
|
|
168
|
-
if (result.rows.length === 0) {
|
|
169
|
-
throw new Error('Configured queue not found in database');
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const row = result.rows[0];
|
|
173
|
-
if (row.lease_time !== 600 || row.retry_limit !== 5) {
|
|
174
|
-
throw new Error('Configuration options not saved correctly');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
passTest('Queue configuration applied successfully');
|
|
178
|
-
} catch (error) {
|
|
179
|
-
failTest(error);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Test 5: Take and Acknowledgment
|
|
185
|
-
*/
|
|
186
|
-
export async function testTakeAndAcknowledgment(client) {
|
|
187
|
-
startTest('Take and Acknowledgment');
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
const queue = 'test-take-ack';
|
|
191
|
-
|
|
192
|
-
// Configure queue first
|
|
193
|
-
await client.queue(queue, {});
|
|
194
|
-
|
|
195
|
-
// Push test messages
|
|
196
|
-
await client.push(`${queue}/ack-partition`, [
|
|
197
|
-
{ message: 'Message to complete' },
|
|
198
|
-
{ message: 'Message to fail' }
|
|
199
|
-
]);
|
|
200
|
-
|
|
201
|
-
await sleep(100);
|
|
202
|
-
|
|
203
|
-
// Take messages
|
|
204
|
-
const messages = [];
|
|
205
|
-
for await (const msg of client.take(`${queue}/ack-partition`, { limit: 2, batch: 2 })) {
|
|
206
|
-
messages.push(msg);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (messages.length !== 2) {
|
|
210
|
-
throw new Error('Expected 2 messages from take');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Acknowledge first as completed
|
|
214
|
-
await client.ack(messages[0], true);
|
|
215
|
-
|
|
216
|
-
// Acknowledge second as failed
|
|
217
|
-
await client.ack(messages[1], false, {
|
|
218
|
-
error: 'Test failure'
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
passTest('Take and acknowledgment work correctly');
|
|
222
|
-
} catch (error) {
|
|
223
|
-
failTest(error);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Test 6: Delayed Processing
|
|
229
|
-
*/
|
|
230
|
-
export async function testDelayedProcessing(client) {
|
|
231
|
-
startTest('Delayed Processing');
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
const queue = 'test-delayed-queue';
|
|
235
|
-
|
|
236
|
-
// Configure queue with delayed processing
|
|
237
|
-
await client.queue(queue, {
|
|
238
|
-
delayedProcessing: 2 // 2 seconds delay
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const startTime = Date.now();
|
|
243
|
-
|
|
244
|
-
// Push message
|
|
245
|
-
await client.push(queue, {
|
|
246
|
-
message: 'Delayed message',
|
|
247
|
-
sentAt: startTime
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// Try to take immediately (should get nothing)
|
|
251
|
-
let gotImmediate = false;
|
|
252
|
-
for await (const msg of client.take(queue, { limit: 1 })) {
|
|
253
|
-
gotImmediate = true;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (gotImmediate) {
|
|
257
|
-
throw new Error('Got message immediately when it should be delayed');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Wait for delay period plus buffer
|
|
261
|
-
await sleep(2500);
|
|
262
|
-
|
|
263
|
-
// Try to take again (should get the message now)
|
|
264
|
-
let gotDelayed = false;
|
|
265
|
-
let processingDelay = 0;
|
|
266
|
-
|
|
267
|
-
for await (const msg of client.take(queue, { limit: 1 })) {
|
|
268
|
-
gotDelayed = true;
|
|
269
|
-
processingDelay = Date.now() - startTime;
|
|
270
|
-
await client.ack(msg);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (!gotDelayed) {
|
|
274
|
-
throw new Error('Did not get delayed message after delay period');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (processingDelay < 2000) {
|
|
278
|
-
throw new Error(`Message processed too early: ${processingDelay}ms`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
passTest(`Delayed processing works correctly (${processingDelay}ms delay)`);
|
|
282
|
-
} catch (error) {
|
|
283
|
-
failTest(error);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Test 7: FIFO Ordering Within Partitions
|
|
289
|
-
*/
|
|
290
|
-
export async function testPartitionFIFOOrdering(client) {
|
|
291
|
-
startTest('FIFO Ordering Within Partitions');
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
const queue = 'test-partition-fifo';
|
|
295
|
-
|
|
296
|
-
// Configure queue
|
|
297
|
-
await client.queue(queue, {
|
|
298
|
-
priority: 0
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
const partitions = [
|
|
302
|
-
{ name: 'ultra-high', priority: 100 },
|
|
303
|
-
{ name: 'high', priority: 10 },
|
|
304
|
-
{ name: 'medium', priority: 5 },
|
|
305
|
-
{ name: 'low', priority: 1 }
|
|
306
|
-
];
|
|
307
|
-
|
|
308
|
-
// Create partitions manually in DB
|
|
309
|
-
for (const partition of partitions) {
|
|
310
|
-
await dbPool.query(`
|
|
311
|
-
INSERT INTO queen.partitions (queue_id, name)
|
|
312
|
-
SELECT q.id, $1
|
|
313
|
-
FROM queen.queues q
|
|
314
|
-
WHERE q.name = $2
|
|
315
|
-
ON CONFLICT (queue_id, name) DO NOTHING
|
|
316
|
-
`, [partition.name, queue]);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Push messages to partitions
|
|
320
|
-
for (const p of partitions) {
|
|
321
|
-
await client.push(`${queue}/${p.name}`, {
|
|
322
|
-
message: `${p.name} priority message`,
|
|
323
|
-
priority: p.priority
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
await sleep(100);
|
|
328
|
-
|
|
329
|
-
// Take messages from different partitions
|
|
330
|
-
const allMessages = [];
|
|
331
|
-
for (let i = 0; i < partitions.length; i++) {
|
|
332
|
-
for await (const msg of client.take(queue, { limit: 1, batch: 1 })) {
|
|
333
|
-
allMessages.push(msg);
|
|
334
|
-
await client.ack(msg);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (allMessages.length !== partitions.length) {
|
|
339
|
-
throw new Error(`Expected ${partitions.length} messages, got ${allMessages.length}`);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
passTest('FIFO ordering within partitions works correctly');
|
|
343
|
-
} catch (error) {
|
|
344
|
-
failTest(error);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Test 8: Window Buffer
|
|
350
|
-
* Verifies that windowBuffer delays message consumption across all access modes
|
|
351
|
-
*/
|
|
352
|
-
export async function testWindowBuffer(client) {
|
|
353
|
-
startTest('Window Buffer');
|
|
354
|
-
|
|
355
|
-
try {
|
|
356
|
-
const timestamp = Date.now();
|
|
357
|
-
const queueName = 'test-window-buffer-' + timestamp;
|
|
358
|
-
const partition = 'test-partition';
|
|
359
|
-
|
|
360
|
-
// Test CASE 1: Direct partition access
|
|
361
|
-
await client.queue(queueName, { windowBuffer: 5 });
|
|
362
|
-
|
|
363
|
-
// Push a message
|
|
364
|
-
await client.push(`${queueName}/${partition}`, { test: 'case1' });
|
|
365
|
-
|
|
366
|
-
// Try to take immediately - should get nothing
|
|
367
|
-
let gotImmediate = false;
|
|
368
|
-
for await (const msg of client.take(`${queueName}/${partition}`, { limit: 1 })) {
|
|
369
|
-
gotImmediate = true;
|
|
370
|
-
await client.ack(msg);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (gotImmediate) {
|
|
374
|
-
throw new Error('Got message immediately in direct access - windowBuffer not working');
|
|
375
|
-
}
|
|
376
|
-
log('Direct partition access: windowBuffer correctly delayed message');
|
|
377
|
-
|
|
378
|
-
// Wait for window to pass
|
|
379
|
-
await sleep(6000);
|
|
380
|
-
|
|
381
|
-
// Now should get the message
|
|
382
|
-
let gotDelayed = false;
|
|
383
|
-
for await (const msg of client.take(`${queueName}/${partition}`, { limit: 1 })) {
|
|
384
|
-
gotDelayed = true;
|
|
385
|
-
await client.ack(msg);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (!gotDelayed) {
|
|
389
|
-
throw new Error('Did not get message after window expired');
|
|
390
|
-
}
|
|
391
|
-
log('Message available after window buffer expired');
|
|
392
|
-
|
|
393
|
-
// Test CASE 2: Queue-level access
|
|
394
|
-
const queue2 = 'test-window-buffer-q2-' + timestamp;
|
|
395
|
-
await client.queue(queue2, { windowBuffer: 5 });
|
|
396
|
-
await client.push(`${queue2}/p1`, { test: 'case2' });
|
|
397
|
-
|
|
398
|
-
let gotCase2Immediate = false;
|
|
399
|
-
for await (const msg of client.take(queue2, { limit: 1 })) {
|
|
400
|
-
gotCase2Immediate = true;
|
|
401
|
-
await client.ack(msg);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (gotCase2Immediate) {
|
|
405
|
-
throw new Error('Got message immediately in queue access - windowBuffer not working');
|
|
406
|
-
}
|
|
407
|
-
log('Queue-level access: windowBuffer correctly delayed message');
|
|
408
|
-
|
|
409
|
-
await sleep(6000);
|
|
410
|
-
|
|
411
|
-
let gotCase2Delayed = false;
|
|
412
|
-
for await (const msg of client.take(queue2, { limit: 1 })) {
|
|
413
|
-
gotCase2Delayed = true;
|
|
414
|
-
await client.ack(msg);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (!gotCase2Delayed) {
|
|
418
|
-
throw new Error('Did not get message after window expired (queue access)');
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Test CASE 3: Namespace/task filtered access
|
|
422
|
-
const ns = `test-ns-${timestamp}`;
|
|
423
|
-
const task = `test-task-${timestamp}`;
|
|
424
|
-
const queue3 = 'test-window-buffer-filtered-' + timestamp;
|
|
425
|
-
|
|
426
|
-
await client.queue(queue3, { windowBuffer: 5 }, { namespace: ns, task: task });
|
|
427
|
-
await client.push(`${queue3}/p1`, { test: 'case3' });
|
|
428
|
-
|
|
429
|
-
let gotCase3Immediate = false;
|
|
430
|
-
for await (const msg of client.take(`namespace:${ns}/task:${task}`, { limit: 1 })) {
|
|
431
|
-
gotCase3Immediate = true;
|
|
432
|
-
await client.ack(msg);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (gotCase3Immediate) {
|
|
436
|
-
throw new Error('Got message immediately in filtered access - windowBuffer not working');
|
|
437
|
-
}
|
|
438
|
-
log('Namespace/task filtered access: windowBuffer correctly delayed message');
|
|
439
|
-
|
|
440
|
-
await sleep(6000);
|
|
441
|
-
|
|
442
|
-
let gotCase3Delayed = false;
|
|
443
|
-
for await (const msg of client.take(`namespace:${ns}/task:${task}`, { limit: 1 })) {
|
|
444
|
-
gotCase3Delayed = true;
|
|
445
|
-
await client.ack(msg);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (!gotCase3Delayed) {
|
|
449
|
-
throw new Error('Did not get message after window expired (filtered access)');
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
passTest('Window buffer works correctly across all access modes');
|
|
453
|
-
} catch (error) {
|
|
454
|
-
failTest(error);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|