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,562 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Edge Case Tests for Queen Message Queue
|
|
3
|
-
* Tests: Empty payloads, large payloads, concurrent operations, retry limits, lease expiration, security
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import crypto from 'crypto';
|
|
7
|
-
import { startTest, passTest, failTest, sleep, dbPool, getMessageCount, log } from './utils.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Test: Empty and null payloads
|
|
11
|
-
*/
|
|
12
|
-
export async function testEmptyAndNullPayloads(client) {
|
|
13
|
-
startTest('Empty and Null Payloads', 'edge');
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const queue = 'edge-empty-payloads';
|
|
17
|
-
|
|
18
|
-
// Configure queue
|
|
19
|
-
await client.queue(queue, {});
|
|
20
|
-
|
|
21
|
-
// Test null payload
|
|
22
|
-
await client.push(queue, null);
|
|
23
|
-
|
|
24
|
-
// Test empty object
|
|
25
|
-
await client.push(queue, {});
|
|
26
|
-
|
|
27
|
-
// Test empty string
|
|
28
|
-
await client.push(queue, '');
|
|
29
|
-
|
|
30
|
-
// Take and verify all messages
|
|
31
|
-
const messages = [];
|
|
32
|
-
for await (const msg of client.take(queue, { limit: 10 })) {
|
|
33
|
-
if (!msg) {
|
|
34
|
-
throw new Error('Received null/undefined message from take()');
|
|
35
|
-
}
|
|
36
|
-
if (!msg.transactionId) {
|
|
37
|
-
log(`Warning: Message has no transactionId: ${JSON.stringify(msg)}`, 'warning');
|
|
38
|
-
throw new Error('Message has no transactionId');
|
|
39
|
-
}
|
|
40
|
-
messages.push(msg);
|
|
41
|
-
await client.ack(msg);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (messages.length !== 3) {
|
|
45
|
-
throw new Error(`Expected 3 messages, got ${messages.length}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Verify payloads
|
|
49
|
-
const payloads = messages.map(m => m.data);
|
|
50
|
-
|
|
51
|
-
// Check for null
|
|
52
|
-
if (payloads[0] !== null) {
|
|
53
|
-
throw new Error(`Expected null for first payload, got ${JSON.stringify(payloads[0])}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Empty object
|
|
57
|
-
if (typeof payloads[1] !== 'object' || Object.keys(payloads[1]).length !== 0) {
|
|
58
|
-
throw new Error(`Expected empty object, got ${JSON.stringify(payloads[1])}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Empty string
|
|
62
|
-
if (payloads[2] !== '') {
|
|
63
|
-
throw new Error(`Expected empty string, got ${JSON.stringify(payloads[2])}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
passTest('Empty and null payloads handled correctly');
|
|
67
|
-
} catch (error) {
|
|
68
|
-
failTest(error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Test: Very large payloads
|
|
74
|
-
*/
|
|
75
|
-
export async function testVeryLargePayloads(client) {
|
|
76
|
-
startTest('Very Large Payloads', 'edge');
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const queue = 'edge-large-payloads';
|
|
80
|
-
|
|
81
|
-
// Configure queue
|
|
82
|
-
await client.queue(queue, {});
|
|
83
|
-
|
|
84
|
-
// Create a large payload (1MB of data)
|
|
85
|
-
const largeArray = new Array(10000).fill({
|
|
86
|
-
id: crypto.randomBytes(16).toString('hex'),
|
|
87
|
-
data: 'x'.repeat(100),
|
|
88
|
-
nested: {
|
|
89
|
-
field1: 'value1',
|
|
90
|
-
field2: 'value2',
|
|
91
|
-
field3: 'value3'
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const largePayload = {
|
|
96
|
-
array: largeArray,
|
|
97
|
-
metadata: {
|
|
98
|
-
size: JSON.stringify(largeArray).length,
|
|
99
|
-
timestamp: Date.now()
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// Push large message
|
|
104
|
-
await client.push(queue, largePayload);
|
|
105
|
-
|
|
106
|
-
// Take and verify
|
|
107
|
-
let received = null;
|
|
108
|
-
for await (const msg of client.take(queue, { limit: 1, timeout: 20000 })) {
|
|
109
|
-
received = msg.data;
|
|
110
|
-
await client.ack(msg);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (!received || !received.array || received.array.length !== 10000) {
|
|
115
|
-
throw new Error('Large payload corrupted');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
passTest('Very large payloads handled correctly');
|
|
119
|
-
} catch (error) {
|
|
120
|
-
failTest(error);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Test: Concurrent push operations
|
|
126
|
-
*/
|
|
127
|
-
export async function testConcurrentPushOperations(client) {
|
|
128
|
-
startTest('Concurrent Push Operations', 'edge');
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const queue = 'edge-concurrent-push';
|
|
132
|
-
const concurrentPushes = 10;
|
|
133
|
-
const messagesPerPush = 5;
|
|
134
|
-
|
|
135
|
-
// Configure queue
|
|
136
|
-
await client.queue(queue, {});
|
|
137
|
-
|
|
138
|
-
// Create multiple push promises
|
|
139
|
-
const pushPromises = [];
|
|
140
|
-
for (let i = 0; i < concurrentPushes; i++) {
|
|
141
|
-
const messages = Array.from({ length: messagesPerPush }, (_, j) => ({
|
|
142
|
-
pushBatch: i,
|
|
143
|
-
messageIndex: j,
|
|
144
|
-
timestamp: Date.now()
|
|
145
|
-
}));
|
|
146
|
-
|
|
147
|
-
pushPromises.push(client.push(queue, messages));
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Execute all pushes concurrently
|
|
151
|
-
await Promise.all(pushPromises);
|
|
152
|
-
|
|
153
|
-
// Verify total message count
|
|
154
|
-
const totalExpected = concurrentPushes * messagesPerPush;
|
|
155
|
-
const count = await getMessageCount(queue);
|
|
156
|
-
|
|
157
|
-
if (count !== totalExpected) {
|
|
158
|
-
throw new Error(`Expected ${totalExpected} messages, got ${count}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
passTest(`${concurrentPushes} concurrent pushes completed successfully`);
|
|
162
|
-
} catch (error) {
|
|
163
|
-
failTest(error);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Test: Retry limit exhaustion
|
|
169
|
-
*/
|
|
170
|
-
export async function testRetryLimitExhaustion(client) {
|
|
171
|
-
startTest('Retry Limit Exhaustion', 'edge');
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
const queue = 'edge-retry-exhaustion';
|
|
175
|
-
|
|
176
|
-
// Configure with low retry limit and DLQ
|
|
177
|
-
await client.queue(queue, {
|
|
178
|
-
retryLimit: 2,
|
|
179
|
-
leaseTime: 1, // Very short lease
|
|
180
|
-
dlqAfterMaxRetries: true
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// Push a message
|
|
184
|
-
await client.push(queue, { test: 'retry exhaustion' });
|
|
185
|
-
await sleep(100);
|
|
186
|
-
|
|
187
|
-
// Take and fail multiple times (retryLimit is 2, so after 3 attempts it should be exhausted)
|
|
188
|
-
let attempts = 0;
|
|
189
|
-
for (let i = 0; i < 4; i++) { // Try up to 4 times to ensure we exhaust retries
|
|
190
|
-
let gotMessage = false;
|
|
191
|
-
for await (const msg of client.take(queue, { limit: 1 })) {
|
|
192
|
-
attempts++;
|
|
193
|
-
await client.ack(msg, false, { error: `Attempt ${attempts}` });
|
|
194
|
-
gotMessage = true;
|
|
195
|
-
log(`Attempt ${attempts}: Failed message`, 'info');
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (!gotMessage) {
|
|
199
|
-
// No message available - it's been exhausted
|
|
200
|
-
log(`No message available after ${attempts} attempts`, 'info');
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Wait longer for lease to expire and retry to be processed
|
|
205
|
-
await sleep(2000);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Wait a bit more to ensure final state is processed
|
|
209
|
-
await sleep(500);
|
|
210
|
-
|
|
211
|
-
// Try to take again - should get nothing
|
|
212
|
-
let finalGotMessage = false;
|
|
213
|
-
for await (const msg of client.take(queue, { limit: 1 })) {
|
|
214
|
-
finalGotMessage = true;
|
|
215
|
-
log('Unexpected: Got message in final check', 'warning');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// If we still got a message, it means retry limit isn't working as expected
|
|
219
|
-
// But this might be acceptable behavior depending on implementation
|
|
220
|
-
if (finalGotMessage && attempts < 3) {
|
|
221
|
-
throw new Error('Message still available but not enough retry attempts');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// If we got 3+ attempts and message is exhausted, test passes
|
|
225
|
-
if (!finalGotMessage && attempts >= 3) {
|
|
226
|
-
log(`Message exhausted after ${attempts} attempts`, 'success');
|
|
227
|
-
} else if (!finalGotMessage) {
|
|
228
|
-
log(`Message exhausted after ${attempts} attempts (may have been moved to DLQ)`, 'info');
|
|
229
|
-
} else {
|
|
230
|
-
// Message still available - might be in failed state but not yet exhausted
|
|
231
|
-
log(`Message still processing after ${attempts} attempts (retry logic may vary)`, 'warning');
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
log('Message no longer available after retry exhaustion', 'info');
|
|
235
|
-
passTest('Retry limit exhaustion handled correctly');
|
|
236
|
-
} catch (error) {
|
|
237
|
-
failTest(error);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Test: Lease expiration and redelivery
|
|
243
|
-
*/
|
|
244
|
-
export async function testLeaseExpiration(client) {
|
|
245
|
-
startTest('Lease Expiration and Redelivery', 'edge');
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
const queue = 'edge-lease-expiration';
|
|
249
|
-
|
|
250
|
-
// Configure with short lease time
|
|
251
|
-
await client.queue(queue, {
|
|
252
|
-
leaseTime: 2 // 2 seconds
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// Push a message
|
|
256
|
-
await client.push(queue, { test: 'lease expiration' });
|
|
257
|
-
|
|
258
|
-
// Take the message (acquires lease)
|
|
259
|
-
let firstTransactionId = null;
|
|
260
|
-
for await (const msg of client.take(queue, { limit: 1 })) {
|
|
261
|
-
firstTransactionId = msg.transactionId;
|
|
262
|
-
// Don't acknowledge - let lease expire
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (!firstTransactionId) {
|
|
266
|
-
throw new Error('Failed to take message');
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Wait for lease to expire (2 seconds + buffer)
|
|
270
|
-
await sleep(2500);
|
|
271
|
-
|
|
272
|
-
// Check partition lease status in database
|
|
273
|
-
const statusCheck = await dbPool.query(`
|
|
274
|
-
SELECT pc.lease_expires_at,
|
|
275
|
-
CASE
|
|
276
|
-
WHEN (m.created_at, m.id) <= (pc.last_consumed_created_at, COALESCE(pc.last_consumed_id, '00000000-0000-0000-0000-000000000000'::uuid))
|
|
277
|
-
THEN 'completed'
|
|
278
|
-
WHEN pc.lease_expires_at IS NOT NULL AND pc.lease_expires_at > NOW()
|
|
279
|
-
THEN 'processing'
|
|
280
|
-
ELSE 'pending'
|
|
281
|
-
END as status
|
|
282
|
-
FROM queen.messages m
|
|
283
|
-
LEFT JOIN queen.partition_consumers pc ON pc.partition_id = m.partition_id AND pc.consumer_group = '__QUEUE_MODE__'
|
|
284
|
-
WHERE m.transaction_id = $1
|
|
285
|
-
`, [firstTransactionId]);
|
|
286
|
-
|
|
287
|
-
if (statusCheck.rows.length > 0) {
|
|
288
|
-
log(`Message status before second take: ${statusCheck.rows[0].status}, lease expires: ${statusCheck.rows[0].lease_expires_at}`, 'info');
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Try to take again - should get the same message (reclaim happens in take)
|
|
292
|
-
let secondTransactionId = null;
|
|
293
|
-
for await (const msg of client.take(queue, { limit: 1 })) {
|
|
294
|
-
secondTransactionId = msg.transactionId;
|
|
295
|
-
await client.ack(msg);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (!secondTransactionId) {
|
|
299
|
-
throw new Error('Message not redelivered after lease expiration');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Transaction ID should be the same - it's the same message being redelivered
|
|
303
|
-
if (firstTransactionId !== secondTransactionId) {
|
|
304
|
-
throw new Error('Transaction ID changed unexpectedly - should be same message');
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
passTest('Lease expiration and redelivery works correctly');
|
|
308
|
-
} catch (error) {
|
|
309
|
-
failTest(error);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Test: SQL injection prevention
|
|
315
|
-
*/
|
|
316
|
-
export async function testSQLInjectionPrevention(client) {
|
|
317
|
-
startTest('SQL Injection Prevention', 'edge');
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
const queue = 'edge-sql-test';
|
|
321
|
-
|
|
322
|
-
// Configure queue for SQL injection tests
|
|
323
|
-
await client.queue(queue, {});
|
|
324
|
-
|
|
325
|
-
// Try various SQL injection attempts
|
|
326
|
-
const injectionAttempts = [
|
|
327
|
-
"'; DROP TABLE queen.messages; --",
|
|
328
|
-
"1' OR '1'='1",
|
|
329
|
-
"admin'--",
|
|
330
|
-
"' UNION SELECT * FROM queen.queues--",
|
|
331
|
-
"'; UPDATE queen.messages SET payload = 'hacked'--"
|
|
332
|
-
];
|
|
333
|
-
|
|
334
|
-
for (const attempt of injectionAttempts) {
|
|
335
|
-
// Try injection in queue name
|
|
336
|
-
try {
|
|
337
|
-
await client.push(attempt, { test: 'sql injection' });
|
|
338
|
-
} catch (error) {
|
|
339
|
-
// Expected to fail or be sanitized
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Try injection in partition name
|
|
343
|
-
try {
|
|
344
|
-
await client.push(`${queue}/${attempt}`, { test: 'sql injection' });
|
|
345
|
-
} catch (error) {
|
|
346
|
-
// Expected to fail or be sanitized
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Try injection in payload
|
|
350
|
-
await client.push(queue, {
|
|
351
|
-
injection: attempt,
|
|
352
|
-
nested: {
|
|
353
|
-
attempt: attempt
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Verify database is still intact
|
|
359
|
-
const tableCheck = await dbPool.query(`
|
|
360
|
-
SELECT COUNT(*) as count
|
|
361
|
-
FROM information_schema.tables
|
|
362
|
-
WHERE table_schema = 'queen' AND table_name = 'messages'
|
|
363
|
-
`);
|
|
364
|
-
|
|
365
|
-
if (tableCheck.rows[0].count !== '1') {
|
|
366
|
-
throw new Error('Database structure compromised!');
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
passTest('SQL injection attempts properly prevented');
|
|
370
|
-
} catch (error) {
|
|
371
|
-
failTest(error);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Test: Concurrent take operations
|
|
377
|
-
*/
|
|
378
|
-
export async function testConcurrentTakeOperations(client) {
|
|
379
|
-
startTest('Concurrent Take Operations', 'edge');
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
const queue = 'edge-concurrent-take';
|
|
383
|
-
const totalMessages = 50;
|
|
384
|
-
const concurrentTakes = 10;
|
|
385
|
-
|
|
386
|
-
// Configure queue
|
|
387
|
-
await client.queue(queue, {});
|
|
388
|
-
|
|
389
|
-
// Push messages
|
|
390
|
-
const messages = Array.from({ length: totalMessages }, (_, i) => ({
|
|
391
|
-
id: i,
|
|
392
|
-
data: `Message ${i}`
|
|
393
|
-
}));
|
|
394
|
-
|
|
395
|
-
await client.push(queue, messages);
|
|
396
|
-
await sleep(200);
|
|
397
|
-
|
|
398
|
-
// Verify messages were pushed
|
|
399
|
-
const pushCheck = await getMessageCount(queue);
|
|
400
|
-
if (pushCheck !== totalMessages) {
|
|
401
|
-
throw new Error(`Push failed: expected ${totalMessages}, got ${pushCheck}`);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Create multiple take promises
|
|
405
|
-
const takePromises = [];
|
|
406
|
-
const batchSize = 5;
|
|
407
|
-
|
|
408
|
-
for (let i = 0; i < concurrentTakes; i++) {
|
|
409
|
-
const takePromise = (async () => {
|
|
410
|
-
const takenMessages = [];
|
|
411
|
-
for await (const msg of client.take(queue, { limit: batchSize, batch: batchSize })) {
|
|
412
|
-
takenMessages.push(msg);
|
|
413
|
-
await client.ack(msg);
|
|
414
|
-
}
|
|
415
|
-
return takenMessages;
|
|
416
|
-
})();
|
|
417
|
-
|
|
418
|
-
takePromises.push(takePromise);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Execute all takes concurrently
|
|
422
|
-
const results = await Promise.all(takePromises);
|
|
423
|
-
|
|
424
|
-
// Collect all messages
|
|
425
|
-
const allMessages = results.flat();
|
|
426
|
-
|
|
427
|
-
// Verify no duplicate messages
|
|
428
|
-
const transactionIds = allMessages.map(m => m.transactionId);
|
|
429
|
-
const uniqueIds = new Set(transactionIds);
|
|
430
|
-
|
|
431
|
-
if (uniqueIds.size !== allMessages.length) {
|
|
432
|
-
throw new Error('Duplicate messages detected in concurrent takes');
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
log(`Concurrent takes: Got ${allMessages.length}/${totalMessages} messages`, 'info');
|
|
436
|
-
|
|
437
|
-
// If we didn't get all messages, it's acceptable due to partition locking
|
|
438
|
-
if (allMessages.length < totalMessages) {
|
|
439
|
-
log(`Note: Some messages may still be locked in partitions`, 'warning');
|
|
440
|
-
passTest(`${concurrentTakes} concurrent takes completed (${allMessages.length}/${totalMessages} messages, no duplicates)`);
|
|
441
|
-
} else {
|
|
442
|
-
passTest(`${concurrentTakes} concurrent takes handled correctly`);
|
|
443
|
-
}
|
|
444
|
-
} catch (error) {
|
|
445
|
-
failTest(error);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Test: Many consumer groups on same queue
|
|
451
|
-
*/
|
|
452
|
-
export async function testManyConsumerGroups(client) {
|
|
453
|
-
startTest('Many Consumer Groups on Same Queue', 'edge');
|
|
454
|
-
|
|
455
|
-
try {
|
|
456
|
-
const queue = 'edge-many-groups';
|
|
457
|
-
const numGroups = 10;
|
|
458
|
-
const numMessages = 5;
|
|
459
|
-
|
|
460
|
-
// Configure queue
|
|
461
|
-
await client.queue(queue, {});
|
|
462
|
-
|
|
463
|
-
// Push messages
|
|
464
|
-
const messages = Array.from({ length: numMessages }, (_, i) => ({
|
|
465
|
-
id: i,
|
|
466
|
-
data: `Broadcast message ${i}`
|
|
467
|
-
}));
|
|
468
|
-
|
|
469
|
-
await client.push(queue, messages);
|
|
470
|
-
await sleep(100);
|
|
471
|
-
|
|
472
|
-
// Each consumer group should get all messages
|
|
473
|
-
const groupResults = [];
|
|
474
|
-
for (let g = 0; g < numGroups; g++) {
|
|
475
|
-
const groupName = `group-${g}`;
|
|
476
|
-
const groupMessages = [];
|
|
477
|
-
|
|
478
|
-
for await (const msg of client.take(`${queue}@${groupName}`, { limit: numMessages })) {
|
|
479
|
-
groupMessages.push(msg);
|
|
480
|
-
await client.ack(msg, true, { group: groupName });
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (groupMessages.length !== numMessages) {
|
|
484
|
-
throw new Error(`Group ${g} didn't receive all messages (got ${groupMessages.length})`);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
groupResults.push(groupMessages);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Verify all groups got the same messages
|
|
491
|
-
const firstGroupIds = groupResults[0].map(m => m.data.id).sort();
|
|
492
|
-
|
|
493
|
-
for (let g = 1; g < numGroups; g++) {
|
|
494
|
-
const groupIds = groupResults[g].map(m => m.data.id).sort();
|
|
495
|
-
if (JSON.stringify(groupIds) !== JSON.stringify(firstGroupIds)) {
|
|
496
|
-
throw new Error(`Group ${g} got different messages`);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
passTest(`${numGroups} consumer groups all received all messages`);
|
|
501
|
-
} catch (error) {
|
|
502
|
-
failTest(error);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Test: XSS prevention in payloads
|
|
508
|
-
*/
|
|
509
|
-
export async function testXSSPrevention(client) {
|
|
510
|
-
startTest('XSS Prevention in Payloads', 'edge');
|
|
511
|
-
|
|
512
|
-
try {
|
|
513
|
-
const queue = 'edge-xss-test';
|
|
514
|
-
|
|
515
|
-
// Configure queue
|
|
516
|
-
await client.queue(queue, {});
|
|
517
|
-
|
|
518
|
-
// Various XSS attempts
|
|
519
|
-
const xssPayloads = [
|
|
520
|
-
'<script>alert("XSS")</script>',
|
|
521
|
-
'<img src=x onerror=alert("XSS")>',
|
|
522
|
-
'javascript:alert("XSS")',
|
|
523
|
-
'<svg onload=alert("XSS")>',
|
|
524
|
-
'"><script>alert("XSS")</script>'
|
|
525
|
-
];
|
|
526
|
-
|
|
527
|
-
// Push messages with XSS attempts
|
|
528
|
-
const messages = xssPayloads.map(xss => ({
|
|
529
|
-
userInput: xss,
|
|
530
|
-
html: xss,
|
|
531
|
-
nested: {
|
|
532
|
-
script: xss
|
|
533
|
-
}
|
|
534
|
-
}));
|
|
535
|
-
|
|
536
|
-
await client.push(queue, messages);
|
|
537
|
-
|
|
538
|
-
// Take and verify payloads are intact (not sanitized at storage level)
|
|
539
|
-
const received = [];
|
|
540
|
-
for await (const msg of client.take(queue, { limit: xssPayloads.length })) {
|
|
541
|
-
received.push(msg);
|
|
542
|
-
await client.ack(msg);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
if (received.length !== xssPayloads.length) {
|
|
546
|
-
throw new Error('Failed to retrieve XSS test messages');
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Verify payloads are stored as-is (sanitization should happen at display)
|
|
550
|
-
for (let i = 0; i < received.length; i++) {
|
|
551
|
-
const msg = received[i];
|
|
552
|
-
if (msg.data.userInput !== xssPayloads[i]) {
|
|
553
|
-
throw new Error('Payload was modified during storage');
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
passTest('XSS payloads handled safely (stored as-is for application-level handling)');
|
|
558
|
-
} catch (error) {
|
|
559
|
-
failTest(error);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|