221 lines
5.5 KiB
TypeScript
221 lines
5.5 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
import { handlerRegistry, QueueManager } from '../src';
|
|
|
|
// Suppress Redis connection errors in tests
|
|
process.on('unhandledRejection', (reason, promise) => {
|
|
if (reason && typeof reason === 'object' && 'message' in reason) {
|
|
const message = (reason as Error).message;
|
|
if (
|
|
message.includes('Connection is closed') ||
|
|
message.includes('Connection is in monitoring mode')
|
|
) {
|
|
// Suppress these specific Redis errors in tests
|
|
return;
|
|
}
|
|
}
|
|
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
});
|
|
|
|
describe('QueueManager Integration Tests', () => {
|
|
let queueManager: QueueManager;
|
|
|
|
// Use local Redis/Dragonfly
|
|
const redisConfig = {
|
|
host: 'localhost',
|
|
port: 6379,
|
|
password: '',
|
|
db: 0,
|
|
};
|
|
|
|
beforeEach(() => {
|
|
handlerRegistry.clear();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
if (queueManager) {
|
|
try {
|
|
await Promise.race([
|
|
queueManager.shutdown(),
|
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Shutdown timeout')), 3000)),
|
|
]);
|
|
} catch (error) {
|
|
// Ignore shutdown errors in tests
|
|
console.warn('Shutdown error:', error.message);
|
|
} finally {
|
|
queueManager = null as any;
|
|
}
|
|
}
|
|
|
|
// Clear handler registry to prevent conflicts
|
|
handlerRegistry.clear();
|
|
|
|
// Add delay to allow connections to close
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
});
|
|
|
|
test('should initialize queue manager', async () => {
|
|
queueManager = new QueueManager({
|
|
queueName: 'test-queue',
|
|
redis: redisConfig,
|
|
workers: 1,
|
|
concurrency: 5,
|
|
});
|
|
|
|
await queueManager.initialize();
|
|
expect(queueManager.queueName).toBe('test-queue');
|
|
});
|
|
|
|
test('should add and process a job', async () => {
|
|
let processedPayload: any;
|
|
|
|
// Register handler
|
|
handlerRegistry.register('test-handler', {
|
|
'test-operation': async payload => {
|
|
processedPayload = payload;
|
|
return { success: true, data: payload };
|
|
},
|
|
});
|
|
|
|
queueManager = new QueueManager({
|
|
queueName: 'test-queue',
|
|
redis: redisConfig,
|
|
workers: 1,
|
|
});
|
|
|
|
await queueManager.initialize();
|
|
|
|
// Add job
|
|
const job = await queueManager.add('test-job', {
|
|
handler: 'test-handler',
|
|
operation: 'test-operation',
|
|
payload: { message: 'Hello, Queue!' },
|
|
});
|
|
|
|
expect(job.name).toBe('test-job');
|
|
|
|
// Wait for processing
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
expect(processedPayload).toEqual({ message: 'Hello, Queue!' });
|
|
});
|
|
|
|
test('should handle job errors with retries', async () => {
|
|
let attemptCount = 0;
|
|
|
|
handlerRegistry.register('retry-handler', {
|
|
'failing-operation': async () => {
|
|
attemptCount++;
|
|
if (attemptCount < 3) {
|
|
throw new Error(`Attempt ${attemptCount} failed`);
|
|
}
|
|
return { success: true };
|
|
},
|
|
});
|
|
|
|
queueManager = new QueueManager({
|
|
queueName: 'test-queue-retry',
|
|
redis: redisConfig,
|
|
workers: 1,
|
|
defaultJobOptions: {
|
|
attempts: 3,
|
|
backoff: {
|
|
type: 'fixed',
|
|
delay: 50,
|
|
},
|
|
},
|
|
});
|
|
|
|
await queueManager.initialize();
|
|
|
|
const job = await queueManager.add('retry-job', {
|
|
handler: 'retry-handler',
|
|
operation: 'failing-operation',
|
|
payload: {},
|
|
});
|
|
|
|
// Wait for retries
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
const completed = await job.isCompleted();
|
|
expect(completed).toBe(true);
|
|
expect(attemptCount).toBe(3);
|
|
});
|
|
|
|
test('should collect metrics when enabled', async () => {
|
|
queueManager = new QueueManager({
|
|
queueName: 'test-queue-metrics',
|
|
redis: redisConfig,
|
|
workers: 0,
|
|
enableMetrics: true,
|
|
});
|
|
|
|
await queueManager.initialize();
|
|
|
|
// Add some jobs
|
|
await queueManager.add('job1', {
|
|
handler: 'test',
|
|
operation: 'test',
|
|
payload: { id: 1 },
|
|
});
|
|
|
|
await queueManager.add('job2', {
|
|
handler: 'test',
|
|
operation: 'test',
|
|
payload: { id: 2 },
|
|
});
|
|
|
|
const metrics = await queueManager.getMetrics();
|
|
expect(metrics).toBeDefined();
|
|
expect(metrics.waiting).toBeDefined();
|
|
expect(metrics.active).toBeDefined();
|
|
expect(metrics.completed).toBeDefined();
|
|
expect(metrics.failed).toBeDefined();
|
|
expect(metrics.processingTime).toBeDefined();
|
|
expect(metrics.throughput).toBeDefined();
|
|
});
|
|
|
|
test('should handle rate limiting when enabled', async () => {
|
|
let processedCount = 0;
|
|
|
|
handlerRegistry.register('rate-limited-handler', {
|
|
'limited-op': async () => {
|
|
processedCount++;
|
|
return { processed: true };
|
|
},
|
|
});
|
|
|
|
queueManager = new QueueManager({
|
|
queueName: 'test-queue-rate',
|
|
redis: redisConfig,
|
|
workers: 1,
|
|
enableRateLimit: true,
|
|
rateLimitRules: [
|
|
{
|
|
level: 'handler',
|
|
handler: 'rate-limited-handler',
|
|
config: {
|
|
points: 2, // 2 requests
|
|
duration: 1, // per 1 second
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
await queueManager.initialize();
|
|
|
|
// Add 3 jobs quickly
|
|
for (let i = 0; i < 3; i++) {
|
|
await queueManager.add(`job${i}`, {
|
|
handler: 'rate-limited-handler',
|
|
operation: 'limited-op',
|
|
payload: { id: i },
|
|
});
|
|
}
|
|
|
|
// Wait for processing
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
|
|
// Only 2 should be processed due to rate limit
|
|
expect(processedCount).toBe(2);
|
|
});
|
|
});
|