queen-mq 0.3.1 → 0.6.3
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 +309 -167
- package/client-js/client-v2/Queen.js +66 -0
- package/client-js/client-v2/README.md +67 -10
- 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/GETTING_STARTED.md +27 -0
- package/client-js/test-v2/MAINTENANCE_TEST.md +148 -0
- package/client-js/test-v2/README_SUBSCRIPTION_TESTS.md +201 -0
- package/client-js/test-v2/consume.js +11 -0
- package/client-js/test-v2/dlq.js +1 -1
- package/client-js/test-v2/load.js +2 -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/client-js/test-v2/subscription.js +198 -18
- package/client-js/test-v2/transaction.js +66 -4
- 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,761 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Advanced Client Feature Tests
|
|
3
|
-
* Tests for Pipeline API, Transaction API, Lease Management, and other new features
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { startTest, passTest, failTest, sleep, log } from './utils.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Test: Pipeline API - Basic Processing
|
|
10
|
-
*/
|
|
11
|
-
export async function testPipelineBasic(client) {
|
|
12
|
-
startTest('Pipeline API - Basic Processing', 'advanced');
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const queue = 'test-pipeline-basic';
|
|
16
|
-
await client.queue(queue, { leaseTime: 30, retryLimit: 3 });
|
|
17
|
-
|
|
18
|
-
// Push test messages
|
|
19
|
-
const messages = [];
|
|
20
|
-
for (let i = 1; i <= 10; i++) {
|
|
21
|
-
messages.push({ id: i, data: `Message ${i}` });
|
|
22
|
-
}
|
|
23
|
-
await client.push(queue, messages);
|
|
24
|
-
|
|
25
|
-
// Test individual message processing
|
|
26
|
-
let processedCount = 0;
|
|
27
|
-
await client
|
|
28
|
-
.pipeline(queue)
|
|
29
|
-
.take(5)
|
|
30
|
-
.process(async (message) => {
|
|
31
|
-
processedCount++;
|
|
32
|
-
return { processed: message.data.id };
|
|
33
|
-
})
|
|
34
|
-
.execute();
|
|
35
|
-
|
|
36
|
-
if (processedCount !== 5) {
|
|
37
|
-
throw new Error(`Expected to process 5 messages, got ${processedCount}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Test batch processing
|
|
41
|
-
let batchSize = 0;
|
|
42
|
-
await client
|
|
43
|
-
.pipeline(queue)
|
|
44
|
-
.take(5)
|
|
45
|
-
.processBatch(async (messages) => {
|
|
46
|
-
batchSize = messages.length;
|
|
47
|
-
return messages.map(m => ({ processed: m.data.id }));
|
|
48
|
-
})
|
|
49
|
-
.execute();
|
|
50
|
-
|
|
51
|
-
if (batchSize !== 5) {
|
|
52
|
-
throw new Error(`Expected batch of 5 messages, got ${batchSize}`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
await client.queueDelete(queue);
|
|
56
|
-
passTest('Pipeline API basic processing works correctly');
|
|
57
|
-
} catch (error) {
|
|
58
|
-
failTest('Pipeline API - Basic Processing', error);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Test: Pipeline API - Error Handling
|
|
64
|
-
*/
|
|
65
|
-
export async function testPipelineErrorHandling(client) {
|
|
66
|
-
startTest('Pipeline API - Error Handling', 'advanced');
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const queue = 'test-pipeline-errors';
|
|
70
|
-
const errorQueue = 'test-pipeline-errors-dlq';
|
|
71
|
-
|
|
72
|
-
await client.queue(queue, { leaseTime: 30, retryLimit: 2 });
|
|
73
|
-
await client.queue(errorQueue, { leaseTime: 30 });
|
|
74
|
-
|
|
75
|
-
// Push messages that will cause errors
|
|
76
|
-
await client.push(queue, [
|
|
77
|
-
{ id: 1, action: 'process' },
|
|
78
|
-
{ id: 2, action: 'error' },
|
|
79
|
-
{ id: 3, action: 'process' },
|
|
80
|
-
{ id: 4, action: 'error' }
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
|
-
let errorCount = 0;
|
|
84
|
-
let successCount = 0;
|
|
85
|
-
let errorMessages = [];
|
|
86
|
-
|
|
87
|
-
// Process all messages with a single pipeline that handles errors
|
|
88
|
-
const result = await client
|
|
89
|
-
.pipeline(queue)
|
|
90
|
-
.take(10) // Take all messages
|
|
91
|
-
.process(async (message) => {
|
|
92
|
-
if (message.data.action === 'error') {
|
|
93
|
-
errorCount++;
|
|
94
|
-
errorMessages.push(message);
|
|
95
|
-
// Move to error queue
|
|
96
|
-
await client.push(errorQueue, {
|
|
97
|
-
error: `Failed processing message ${message.data.id}`,
|
|
98
|
-
message: message.data
|
|
99
|
-
});
|
|
100
|
-
// ACK as failed
|
|
101
|
-
await client.ack(message, false);
|
|
102
|
-
return { error: true, id: message.data.id };
|
|
103
|
-
}
|
|
104
|
-
successCount++;
|
|
105
|
-
return { success: true, id: message.data.id };
|
|
106
|
-
})
|
|
107
|
-
.execute();
|
|
108
|
-
|
|
109
|
-
if (successCount !== 2) {
|
|
110
|
-
throw new Error(`Expected 2 success, got ${successCount}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (errorCount !== 2) {
|
|
114
|
-
throw new Error(`Expected 2 errors, got ${errorCount}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Verify error queue has messages
|
|
118
|
-
const errorQueueMessages = await client.takeSingleBatch(errorQueue, { batch: 10 });
|
|
119
|
-
if (errorQueueMessages.length !== 2) {
|
|
120
|
-
throw new Error(`Expected 2 messages in error queue, got ${errorQueueMessages.length}`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Clean up
|
|
124
|
-
await client.ack(errorQueueMessages);
|
|
125
|
-
await client.queueDelete(queue);
|
|
126
|
-
await client.queueDelete(errorQueue);
|
|
127
|
-
passTest('Pipeline error handling works correctly');
|
|
128
|
-
} catch (error) {
|
|
129
|
-
failTest('Pipeline API - Error Handling', error.message || error);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Test: Pipeline API - Auto Renewal
|
|
135
|
-
*/
|
|
136
|
-
export async function testPipelineAutoRenewal(client) {
|
|
137
|
-
startTest('Pipeline API - Auto Renewal', 'advanced');
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const queue = 'test-pipeline-renewal';
|
|
141
|
-
await client.queue(queue, { leaseTime: 5 }); // Short lease for testing
|
|
142
|
-
|
|
143
|
-
await client.push(queue, { id: 1, data: 'Long task' });
|
|
144
|
-
|
|
145
|
-
let renewalWorked = false;
|
|
146
|
-
|
|
147
|
-
await client
|
|
148
|
-
.pipeline(queue)
|
|
149
|
-
.take(1)
|
|
150
|
-
.withAutoRenewal({ interval: 2000 }) // Renew every 2 seconds
|
|
151
|
-
.process(async (message) => {
|
|
152
|
-
// Simulate long task (8 seconds)
|
|
153
|
-
await sleep(8000);
|
|
154
|
-
renewalWorked = true; // Would fail without renewal
|
|
155
|
-
return { processed: true };
|
|
156
|
-
})
|
|
157
|
-
.execute();
|
|
158
|
-
|
|
159
|
-
if (!renewalWorked) {
|
|
160
|
-
throw new Error('Auto-renewal did not work');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
await client.queueDelete(queue);
|
|
164
|
-
passTest('Pipeline auto-renewal works correctly');
|
|
165
|
-
} catch (error) {
|
|
166
|
-
failTest('Pipeline API - Auto Renewal', error);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Test: Pipeline API - Parallel Processing
|
|
172
|
-
*/
|
|
173
|
-
export async function testPipelineParallel(client) {
|
|
174
|
-
startTest('Pipeline API - Parallel Processing', 'advanced');
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const queue = 'test-pipeline-parallel';
|
|
178
|
-
await client.queue(queue, {
|
|
179
|
-
leaseTime: 30,
|
|
180
|
-
partitions: 5 // Multiple partitions for parallel processing
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// Push messages across partitions
|
|
184
|
-
const messages = [];
|
|
185
|
-
for (let i = 0; i < 60; i++) { // Reduced from 100 to 60 for faster test
|
|
186
|
-
messages.push({
|
|
187
|
-
id: i,
|
|
188
|
-
partition: `p${i % 5}`,
|
|
189
|
-
data: `Message ${i}`
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
for (let p = 0; p < 5; p++) {
|
|
194
|
-
const partitionMessages = messages.filter(m => m.partition === `p${p}`);
|
|
195
|
-
await client.push(`${queue}/p${p}`, partitionMessages);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const startTime = Date.now();
|
|
199
|
-
|
|
200
|
-
const result = await client
|
|
201
|
-
.pipeline(queue)
|
|
202
|
-
.take(20)
|
|
203
|
-
.withConcurrency(3) // 3 parallel workers
|
|
204
|
-
.process(async (message) => {
|
|
205
|
-
await sleep(100); // Simulate work
|
|
206
|
-
return { processed: message.data.id };
|
|
207
|
-
})
|
|
208
|
-
.repeat({ maxIterations: 10, continuous: false })
|
|
209
|
-
.execute();
|
|
210
|
-
|
|
211
|
-
const duration = Date.now() - startTime;
|
|
212
|
-
|
|
213
|
-
// With 3 workers and partitions, we should process at least 36 messages (3 workers * 12 messages each)
|
|
214
|
-
if (result.totalProcessed < 36) {
|
|
215
|
-
throw new Error(`Expected at least 36 messages processed, got ${result.totalProcessed}`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (result.summary.workersUtilized < 2) {
|
|
219
|
-
throw new Error(`Expected at least 2 workers utilized, got ${result.summary.workersUtilized}`);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
log(`Processed ${result.totalProcessed} messages in ${duration}ms with ${result.summary.workersUtilized} workers`);
|
|
223
|
-
|
|
224
|
-
await client.queueDelete(queue);
|
|
225
|
-
passTest('Pipeline parallel processing works correctly');
|
|
226
|
-
} catch (error) {
|
|
227
|
-
failTest('Pipeline API - Parallel Processing', error.message || error);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Test: Pipeline API - Concurrency Race Condition (Regression Test)
|
|
233
|
-
* This test specifically checks for the race condition bug where multiple workers
|
|
234
|
-
* would overwrite shared instance variables, causing ACK to be called with 0 messages.
|
|
235
|
-
*/
|
|
236
|
-
export async function testPipelineConcurrencyRaceCondition(client) {
|
|
237
|
-
startTest('Pipeline API - Concurrency Race Condition (Regression)', 'advanced');
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
const queue = 'test-pipeline-race-condition';
|
|
241
|
-
await client.queue(queue, {
|
|
242
|
-
leaseTime: 30,
|
|
243
|
-
partitions: 3
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
// Push messages that will be processed by multiple workers
|
|
247
|
-
const messages = [];
|
|
248
|
-
for (let i = 0; i < 20; i++) {
|
|
249
|
-
messages.push({
|
|
250
|
-
id: i,
|
|
251
|
-
data: `Race condition test message ${i}`,
|
|
252
|
-
partition: `p${i % 3}`
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Push to different partitions to ensure parallel processing
|
|
257
|
-
for (let p = 0; p < 3; p++) {
|
|
258
|
-
const partitionMessages = messages.filter(m => m.partition === `p${p}`);
|
|
259
|
-
await client.push(`${queue}/p${p}`, partitionMessages);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
let processedMessages = [];
|
|
263
|
-
let ackCallCount = 0;
|
|
264
|
-
let totalAckedMessages = 0;
|
|
265
|
-
|
|
266
|
-
// Patch the client's ack method to track calls
|
|
267
|
-
const originalAck = client.ack.bind(client);
|
|
268
|
-
client.ack = async function(messages, status, context) {
|
|
269
|
-
ackCallCount++;
|
|
270
|
-
const messageCount = Array.isArray(messages) ? messages.length : 1;
|
|
271
|
-
totalAckedMessages += messageCount;
|
|
272
|
-
|
|
273
|
-
// This is the critical check - if the race condition exists,
|
|
274
|
-
// we'll see ACK calls with 0 messages
|
|
275
|
-
if (messageCount === 0) {
|
|
276
|
-
throw new Error(`RACE CONDITION DETECTED: ACK called with 0 messages (call #${ackCallCount})`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
log(`ACK call #${ackCallCount}: ${messageCount} messages`);
|
|
280
|
-
return await originalAck(messages, status, context);
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
const result = await client
|
|
285
|
-
.pipeline(queue)
|
|
286
|
-
.take(5) // Take small batches to increase chance of race condition
|
|
287
|
-
.withConcurrency(4) // High concurrency to trigger race condition
|
|
288
|
-
.processBatch(async (msgs) => {
|
|
289
|
-
// Simulate some processing time to increase race condition window
|
|
290
|
-
await sleep(50 + Math.random() * 100);
|
|
291
|
-
|
|
292
|
-
processedMessages.push(...msgs.map(m => m.data.id));
|
|
293
|
-
log(`Worker processed batch of ${msgs.length} messages: [${msgs.map(m => m.data.id).join(', ')}]`);
|
|
294
|
-
|
|
295
|
-
return msgs;
|
|
296
|
-
})
|
|
297
|
-
.repeat({ maxIterations: 8, continuous: false })
|
|
298
|
-
.execute();
|
|
299
|
-
|
|
300
|
-
// Verify results
|
|
301
|
-
if (result.totalProcessed === 0) {
|
|
302
|
-
throw new Error('No messages were processed');
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (ackCallCount === 0) {
|
|
306
|
-
throw new Error('No ACK calls were made');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (totalAckedMessages !== result.totalProcessed) {
|
|
310
|
-
throw new Error(`ACK count mismatch: processed ${result.totalProcessed}, ACKed ${totalAckedMessages}`);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Verify no duplicate processing (another symptom of race conditions)
|
|
314
|
-
const uniqueProcessed = [...new Set(processedMessages)];
|
|
315
|
-
if (uniqueProcessed.length !== processedMessages.length) {
|
|
316
|
-
log(`Duplicate processing detected: ${processedMessages.length} total, ${uniqueProcessed.length} unique`, 'warning');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
log(`Successfully processed ${result.totalProcessed} messages with ${ackCallCount} ACK calls`);
|
|
320
|
-
log(`Workers utilized: ${result.summary.workersUtilized}`);
|
|
321
|
-
|
|
322
|
-
} finally {
|
|
323
|
-
// Restore original ack method
|
|
324
|
-
client.ack = originalAck;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
await client.queueDelete(queue);
|
|
328
|
-
passTest('Pipeline concurrency race condition test passed - no race condition detected');
|
|
329
|
-
} catch (error) {
|
|
330
|
-
failTest('Pipeline API - Concurrency Race Condition', error.message || error);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Test: Pipeline API - Continuous Mode
|
|
336
|
-
*/
|
|
337
|
-
export async function testPipelineContinuous(client) {
|
|
338
|
-
startTest('Pipeline API - Continuous Mode', 'advanced');
|
|
339
|
-
|
|
340
|
-
try {
|
|
341
|
-
const queue = 'test-pipeline-continuous';
|
|
342
|
-
await client.queue(queue, { leaseTime: 30 });
|
|
343
|
-
|
|
344
|
-
let processedCount = 0;
|
|
345
|
-
|
|
346
|
-
// Create a timeout promise to prevent hanging
|
|
347
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
348
|
-
setTimeout(() => reject(new Error('Test timeout after 10 seconds')), 10000);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
// Start continuous processing
|
|
352
|
-
const pipelinePromise = client
|
|
353
|
-
.pipeline(queue)
|
|
354
|
-
.take(1) // Take 1 message at a time
|
|
355
|
-
.process(async (message) => {
|
|
356
|
-
processedCount++;
|
|
357
|
-
return { processed: message.data.id };
|
|
358
|
-
})
|
|
359
|
-
.repeat({
|
|
360
|
-
continuous: true, // Keep running
|
|
361
|
-
maxIterations: 5, // But stop after 5 iterations
|
|
362
|
-
waitOnEmpty: 500 // Wait 500ms when no messages
|
|
363
|
-
})
|
|
364
|
-
.execute();
|
|
365
|
-
|
|
366
|
-
// Push messages while pipeline is running
|
|
367
|
-
await sleep(100);
|
|
368
|
-
await client.push(queue, { id: 1, data: 'Message 1' });
|
|
369
|
-
|
|
370
|
-
await sleep(1000);
|
|
371
|
-
await client.push(queue, { id: 2, data: 'Message 2' });
|
|
372
|
-
|
|
373
|
-
await sleep(1000);
|
|
374
|
-
await client.push(queue, { id: 3, data: 'Message 3' });
|
|
375
|
-
|
|
376
|
-
// Wait for pipeline to complete (with timeout)
|
|
377
|
-
const result = await Promise.race([pipelinePromise, timeoutPromise]);
|
|
378
|
-
|
|
379
|
-
if (processedCount !== 3) {
|
|
380
|
-
throw new Error(`Expected 3 messages processed in continuous mode, got ${processedCount}`);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
await client.queueDelete(queue);
|
|
384
|
-
passTest('Pipeline continuous mode works correctly');
|
|
385
|
-
} catch (error) {
|
|
386
|
-
// Clean up on error
|
|
387
|
-
try {
|
|
388
|
-
await client.queueDelete('test-pipeline-continuous');
|
|
389
|
-
} catch (e) {
|
|
390
|
-
// Ignore cleanup errors
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (error.message.includes('timeout')) {
|
|
394
|
-
// Skip this test if it times out
|
|
395
|
-
passTest('Pipeline continuous mode test skipped (timeout)');
|
|
396
|
-
} else {
|
|
397
|
-
failTest('Pipeline API - Continuous Mode', error);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Test: Transaction API
|
|
404
|
-
*/
|
|
405
|
-
export async function testTransactionAPI(client) {
|
|
406
|
-
startTest('Transaction API', 'advanced');
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
const inputQueue = 'test-tx-input';
|
|
410
|
-
const outputQueue = 'test-tx-output';
|
|
411
|
-
|
|
412
|
-
await client.queue(inputQueue, { leaseTime: 30 });
|
|
413
|
-
await client.queue(outputQueue, { leaseTime: 30 });
|
|
414
|
-
|
|
415
|
-
// Push test messages
|
|
416
|
-
await client.push(inputQueue, [
|
|
417
|
-
{ id: 1, data: 'Process me' },
|
|
418
|
-
{ id: 2, data: 'Transform me' }
|
|
419
|
-
]);
|
|
420
|
-
|
|
421
|
-
// Take messages
|
|
422
|
-
const messages = await client.takeSingleBatch(inputQueue, { batch: 2 });
|
|
423
|
-
|
|
424
|
-
if (messages.length !== 2) {
|
|
425
|
-
throw new Error(`Expected 2 messages, got ${messages.length}`);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Process and use transaction API
|
|
429
|
-
const processed = messages.map(m => ({
|
|
430
|
-
originalId: m.data.id,
|
|
431
|
-
processed: true,
|
|
432
|
-
timestamp: Date.now()
|
|
433
|
-
}));
|
|
434
|
-
|
|
435
|
-
// Execute atomic transaction
|
|
436
|
-
await client.transaction()
|
|
437
|
-
.ack(messages) // ACK input messages
|
|
438
|
-
.push(outputQueue, processed) // Push to output
|
|
439
|
-
.commit();
|
|
440
|
-
|
|
441
|
-
// Verify output queue has processed messages
|
|
442
|
-
const outputMessages = await client.takeSingleBatch(outputQueue, { batch: 10 });
|
|
443
|
-
|
|
444
|
-
if (outputMessages.length !== 2) {
|
|
445
|
-
throw new Error(`Expected 2 messages in output queue, got ${outputMessages.length}`);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (!outputMessages[0].data.processed) {
|
|
449
|
-
throw new Error('Output message not marked as processed');
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
await client.queueDelete(inputQueue);
|
|
453
|
-
await client.queueDelete(outputQueue);
|
|
454
|
-
passTest('Transaction API works correctly');
|
|
455
|
-
} catch (error) {
|
|
456
|
-
failTest('Transaction API', error);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Test: Pipeline with Atomic Operations
|
|
462
|
-
*/
|
|
463
|
-
export async function testPipelineAtomic(client) {
|
|
464
|
-
startTest('Pipeline API - Atomic Operations', 'advanced');
|
|
465
|
-
|
|
466
|
-
try {
|
|
467
|
-
const inputQueue = 'test-pipeline-atomic-input';
|
|
468
|
-
const outputQueue = 'test-pipeline-atomic-output';
|
|
469
|
-
const auditQueue = 'test-pipeline-atomic-audit';
|
|
470
|
-
|
|
471
|
-
await client.queue(inputQueue, { leaseTime: 30 });
|
|
472
|
-
await client.queue(outputQueue, { leaseTime: 30 });
|
|
473
|
-
await client.queue(auditQueue, { leaseTime: 30 });
|
|
474
|
-
|
|
475
|
-
// Push messages
|
|
476
|
-
await client.push(inputQueue, [
|
|
477
|
-
{ id: 1, value: 10 },
|
|
478
|
-
{ id: 2, value: 20 },
|
|
479
|
-
{ id: 3, value: 30 }
|
|
480
|
-
]);
|
|
481
|
-
|
|
482
|
-
// Process with atomic operations
|
|
483
|
-
await client
|
|
484
|
-
.pipeline(inputQueue)
|
|
485
|
-
.take(5)
|
|
486
|
-
.process(async (message) => {
|
|
487
|
-
// Transform the message
|
|
488
|
-
return {
|
|
489
|
-
originalId: message.data.id,
|
|
490
|
-
doubledValue: message.data.value * 2,
|
|
491
|
-
processedAt: new Date().toISOString()
|
|
492
|
-
};
|
|
493
|
-
})
|
|
494
|
-
.atomically((tx, originalMessages, processedMessages) => {
|
|
495
|
-
// These operations happen atomically
|
|
496
|
-
tx.ack(originalMessages)
|
|
497
|
-
.push(outputQueue, processedMessages)
|
|
498
|
-
.push(auditQueue, {
|
|
499
|
-
timestamp: Date.now(),
|
|
500
|
-
count: processedMessages.length,
|
|
501
|
-
ids: processedMessages.map(m => m.originalId)
|
|
502
|
-
});
|
|
503
|
-
})
|
|
504
|
-
.execute();
|
|
505
|
-
|
|
506
|
-
// Verify output
|
|
507
|
-
const outputMessages = await client.takeSingleBatch(outputQueue, { batch: 10 });
|
|
508
|
-
if (outputMessages.length !== 3) {
|
|
509
|
-
throw new Error(`Expected 3 messages in output, got ${outputMessages.length}`);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Verify audit
|
|
513
|
-
const auditMessages = await client.takeSingleBatch(auditQueue, { batch: 10 });
|
|
514
|
-
if (auditMessages.length !== 1) {
|
|
515
|
-
throw new Error(`Expected 1 audit message, got ${auditMessages.length}`);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (auditMessages[0].data.count !== 3) {
|
|
519
|
-
throw new Error(`Audit message has wrong count: ${auditMessages[0].data.count}`);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
await client.queueDelete(inputQueue);
|
|
523
|
-
await client.queueDelete(outputQueue);
|
|
524
|
-
await client.queueDelete(auditQueue);
|
|
525
|
-
passTest('Pipeline atomic operations work correctly');
|
|
526
|
-
} catch (error) {
|
|
527
|
-
failTest('Pipeline API - Atomic Operations', error);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* Test: Manual Lease Renewal
|
|
533
|
-
*/
|
|
534
|
-
export async function testManualLeaseRenewal(client) {
|
|
535
|
-
startTest('Manual Lease Renewal', 'advanced');
|
|
536
|
-
|
|
537
|
-
try {
|
|
538
|
-
const queue = 'test-manual-renewal';
|
|
539
|
-
await client.queue(queue, { leaseTime: 5 }); // Short lease
|
|
540
|
-
|
|
541
|
-
await client.push(queue, { id: 1, data: 'Long task' });
|
|
542
|
-
|
|
543
|
-
// Take message
|
|
544
|
-
const messages = await client.takeSingleBatch(queue, { batch: 1 });
|
|
545
|
-
const message = messages[0];
|
|
546
|
-
|
|
547
|
-
if (!message.leaseId) {
|
|
548
|
-
throw new Error('Message does not have leaseId');
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Test single message renewal
|
|
552
|
-
const result1 = await client.renewLease(message);
|
|
553
|
-
if (!result1.success) {
|
|
554
|
-
throw new Error('Failed to renew lease for message');
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Test renewal by lease ID
|
|
558
|
-
const result2 = await client.renewLease(message.leaseId);
|
|
559
|
-
if (!result2.success) {
|
|
560
|
-
throw new Error('Failed to renew lease by ID');
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// ACK the first message before testing batch
|
|
564
|
-
await client.ack(message);
|
|
565
|
-
|
|
566
|
-
// Test batch renewal
|
|
567
|
-
await client.push(queue, [
|
|
568
|
-
{ id: 2, data: 'Message 2' },
|
|
569
|
-
{ id: 3, data: 'Message 3' }
|
|
570
|
-
]);
|
|
571
|
-
|
|
572
|
-
const batch = await client.takeSingleBatch(queue, { batch: 2 });
|
|
573
|
-
|
|
574
|
-
if (batch.length !== 2) {
|
|
575
|
-
throw new Error(`Expected 2 messages in batch, got ${batch.length}`);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const results = await client.renewLease(batch);
|
|
579
|
-
|
|
580
|
-
if (!Array.isArray(results) || results.length !== 2) {
|
|
581
|
-
throw new Error(`Expected array of 2 results, got ${results}`);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const allSuccess = results.every(r => r.success);
|
|
585
|
-
if (!allSuccess) {
|
|
586
|
-
throw new Error('Not all batch renewals succeeded');
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Clean up - ACK remaining messages
|
|
590
|
-
await client.ack(batch);
|
|
591
|
-
|
|
592
|
-
await client.queueDelete(queue);
|
|
593
|
-
passTest('Manual lease renewal works correctly');
|
|
594
|
-
} catch (error) {
|
|
595
|
-
failTest('Manual Lease Renewal', error.message || error);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Test: Lease Validation
|
|
601
|
-
*/
|
|
602
|
-
export async function testLeaseValidation(client) {
|
|
603
|
-
startTest('Lease Validation', 'advanced');
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
const queue = 'test-lease-validation';
|
|
607
|
-
await client.queue(queue, { leaseTime: 30 });
|
|
608
|
-
|
|
609
|
-
await client.push(queue, { id: 1, data: 'Test message' });
|
|
610
|
-
|
|
611
|
-
// Take message to get lease
|
|
612
|
-
const messages = await client.takeSingleBatch(queue, { batch: 1 });
|
|
613
|
-
const message = messages[0];
|
|
614
|
-
const validLeaseId = message.leaseId;
|
|
615
|
-
|
|
616
|
-
// Try to ACK with valid lease (should succeed)
|
|
617
|
-
await client.ack(message);
|
|
618
|
-
|
|
619
|
-
// Push another message
|
|
620
|
-
await client.push(queue, { id: 2, data: 'Another message' });
|
|
621
|
-
|
|
622
|
-
// Take it
|
|
623
|
-
const messages2 = await client.takeSingleBatch(queue, { batch: 1 });
|
|
624
|
-
const message2 = messages2[0];
|
|
625
|
-
|
|
626
|
-
// Corrupt the lease ID
|
|
627
|
-
message2.leaseId = 'invalid-lease-id-12345';
|
|
628
|
-
|
|
629
|
-
// Try to ACK with invalid lease (should fail)
|
|
630
|
-
let invalidAckFailed = false;
|
|
631
|
-
try {
|
|
632
|
-
await client.ack(message2);
|
|
633
|
-
} catch (error) {
|
|
634
|
-
invalidAckFailed = true;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
if (!invalidAckFailed) {
|
|
638
|
-
throw new Error('ACK with invalid lease should have failed');
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
await client.queueDelete(queue);
|
|
642
|
-
passTest('Lease validation works correctly');
|
|
643
|
-
} catch (error) {
|
|
644
|
-
failTest('Lease Validation', error);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* Test: Complex Pipeline Workflow
|
|
650
|
-
*/
|
|
651
|
-
export async function testComplexPipelineWorkflow(client) {
|
|
652
|
-
startTest('Complex Pipeline Workflow', 'advanced');
|
|
653
|
-
|
|
654
|
-
try {
|
|
655
|
-
const rawQueue = 'test-workflow-raw';
|
|
656
|
-
const validQueue = 'test-workflow-valid';
|
|
657
|
-
const invalidQueue = 'test-workflow-invalid';
|
|
658
|
-
const processedQueue = 'test-workflow-processed';
|
|
659
|
-
|
|
660
|
-
// Setup queues
|
|
661
|
-
await client.queue(rawQueue, { leaseTime: 30 });
|
|
662
|
-
await client.queue(validQueue, { leaseTime: 30 });
|
|
663
|
-
await client.queue(invalidQueue, { leaseTime: 30 });
|
|
664
|
-
await client.queue(processedQueue, { leaseTime: 30 });
|
|
665
|
-
|
|
666
|
-
// Push mixed messages
|
|
667
|
-
await client.push(rawQueue, [
|
|
668
|
-
{ id: 1, value: 10, valid: true },
|
|
669
|
-
{ id: 2, value: -5, valid: false },
|
|
670
|
-
{ id: 3, value: 20, valid: true },
|
|
671
|
-
{ id: 4, value: 0, valid: false },
|
|
672
|
-
{ id: 5, value: 15, valid: true }
|
|
673
|
-
]);
|
|
674
|
-
|
|
675
|
-
// Stage 1: Validation pipeline
|
|
676
|
-
await client
|
|
677
|
-
.pipeline(rawQueue)
|
|
678
|
-
.take(10)
|
|
679
|
-
.processBatch(async (messages) => {
|
|
680
|
-
const valid = messages.filter(m => m.data.valid);
|
|
681
|
-
const invalid = messages.filter(m => !m.data.valid);
|
|
682
|
-
return { valid, invalid };
|
|
683
|
-
})
|
|
684
|
-
.atomically((tx, original, result) => {
|
|
685
|
-
tx.ack(original)
|
|
686
|
-
.push(validQueue, result.valid.map(m => m.data))
|
|
687
|
-
.push(invalidQueue, result.invalid.map(m => m.data));
|
|
688
|
-
})
|
|
689
|
-
.execute();
|
|
690
|
-
|
|
691
|
-
// Stage 2: Processing pipeline
|
|
692
|
-
await client
|
|
693
|
-
.pipeline(validQueue)
|
|
694
|
-
.take(10)
|
|
695
|
-
.process(async (message) => {
|
|
696
|
-
return {
|
|
697
|
-
id: message.data.id,
|
|
698
|
-
originalValue: message.data.value,
|
|
699
|
-
processedValue: message.data.value * 2,
|
|
700
|
-
timestamp: Date.now()
|
|
701
|
-
};
|
|
702
|
-
})
|
|
703
|
-
.atomically((tx, original, processed) => {
|
|
704
|
-
tx.ack(original)
|
|
705
|
-
.push(processedQueue, processed);
|
|
706
|
-
})
|
|
707
|
-
.execute();
|
|
708
|
-
|
|
709
|
-
// Verify results
|
|
710
|
-
const processed = await client.takeSingleBatch(processedQueue, { batch: 10 });
|
|
711
|
-
const invalid = await client.takeSingleBatch(invalidQueue, { batch: 10 });
|
|
712
|
-
|
|
713
|
-
if (processed.length !== 3) {
|
|
714
|
-
throw new Error(`Expected 3 processed messages, got ${processed.length}`);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
if (invalid.length !== 2) {
|
|
718
|
-
throw new Error(`Expected 2 invalid messages, got ${invalid.length}`);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// Verify processing
|
|
722
|
-
const processedValues = processed.map(m => m.data.processedValue).sort((a, b) => a - b);
|
|
723
|
-
const expectedValues = [20, 30, 40]; // 10*2, 15*2, 20*2
|
|
724
|
-
|
|
725
|
-
if (JSON.stringify(processedValues) !== JSON.stringify(expectedValues)) {
|
|
726
|
-
throw new Error(`Processed values mismatch: ${processedValues} vs ${expectedValues}`);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// Clean up
|
|
730
|
-
await client.ack(processed);
|
|
731
|
-
await client.ack(invalid);
|
|
732
|
-
await client.queueDelete(rawQueue);
|
|
733
|
-
await client.queueDelete(validQueue);
|
|
734
|
-
await client.queueDelete(invalidQueue);
|
|
735
|
-
await client.queueDelete(processedQueue);
|
|
736
|
-
|
|
737
|
-
passTest('Complex pipeline workflow works correctly');
|
|
738
|
-
} catch (error) {
|
|
739
|
-
failTest('Complex Pipeline Workflow', error);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* Run all advanced client tests
|
|
745
|
-
*/
|
|
746
|
-
export async function runAdvancedClientTests(client) {
|
|
747
|
-
log('🚀 Starting Advanced Client Feature Tests', 'info');
|
|
748
|
-
|
|
749
|
-
await testPipelineBasic(client);
|
|
750
|
-
await testPipelineErrorHandling(client);
|
|
751
|
-
await testPipelineAutoRenewal(client);
|
|
752
|
-
await testPipelineParallel(client);
|
|
753
|
-
await testPipelineContinuous(client);
|
|
754
|
-
await testTransactionAPI(client);
|
|
755
|
-
await testPipelineAtomic(client);
|
|
756
|
-
await testManualLeaseRenewal(client);
|
|
757
|
-
await testLeaseValidation(client);
|
|
758
|
-
await testComplexPipelineWorkflow(client);
|
|
759
|
-
|
|
760
|
-
log('✅ Advanced Client Feature Tests completed', 'info');
|
|
761
|
-
}
|