#!/usr/bin/env node /** * End-to-end test script for queue worker. * * This script: * 1. Connects to Redis * 2. Adds test jobs to the test queue * 3. Waits for them to complete or fail * 4. Reports success/failure * * Usage: * npm run test * DATABASE_REDIS_URL=redis://:password@host:port npm run test */ import { Queue } from 'bullmq'; import Redis from 'ioredis'; import { getServiceRegistry } from '@lilith/service-registry'; interface TestJobData { testId: string; message: string; shouldFail?: boolean; delayMs?: number; } interface TestResult { jobId: string; status: 'completed' | 'failed' | 'timeout'; result?: unknown; error?: string; durationMs: number; } class QueueTester { private redis: Redis; private queue: Queue; private readonly redisUrl: string; private readonly queueName = 'test'; constructor() { // Use env var override or service registry let redisUrl = process.env['DATABASE_REDIS_URL']; if (!redisUrl) { const registry = getServiceRegistry(); redisUrl = registry.getRedisUrl('queue-worker'); if (!redisUrl) { throw new Error('Redis URL not found for queue-worker service in service registry and DATABASE_REDIS_URL not set'); } } this.redisUrl = redisUrl; // Create Redis connection this.redis = new Redis(this.redisUrl, { maxRetriesPerRequest: null, enableReadyCheck: true, }); // Create test queue this.queue = new Queue(this.queueName, { connection: this.redis, }); } async checkConnection(): Promise { console.log('๐Ÿ”Œ Connecting to Redis...'); try { await this.redis.ping(); console.log('โœ… Redis connection successful'); } catch (error) { throw new Error(`Failed to connect to Redis: ${error instanceof Error ? error.message : String(error)}`); } } async addJob(data: TestJobData): Promise { const job = await this.queue.add('test-job', data); return job.id!; } async waitForJob(jobId: string, timeoutMs: number = 10000): Promise { const startTime = Date.now(); return new Promise((resolve) => { const checkInterval = setInterval(async () => { const job = await this.queue.getJob(jobId); if (!job) { clearInterval(checkInterval); resolve({ jobId, status: 'failed', error: 'Job not found', durationMs: Date.now() - startTime, }); return; } const state = await job.getState(); if (state === 'completed') { clearInterval(checkInterval); resolve({ jobId, status: 'completed', result: job.returnvalue, durationMs: Date.now() - startTime, }); return; } if (state === 'failed') { clearInterval(checkInterval); resolve({ jobId, status: 'failed', error: job.failedReason ?? 'Unknown error', durationMs: Date.now() - startTime, }); return; } if (Date.now() - startTime > timeoutMs) { clearInterval(checkInterval); resolve({ jobId, status: 'timeout', error: `Job did not complete within ${timeoutMs}ms`, durationMs: Date.now() - startTime, }); } }, 100); }); } async runTests(): Promise { const results: TestResult[] = []; console.log('\n๐Ÿงช Running queue worker tests...\n'); // Test 1: Simple successful job try { console.log('Test 1: Simple successful job'); const jobId = await this.addJob({ testId: 'test-1', message: 'Hello, queue worker!', }); console.log(` Added job ${jobId}`); const result = await this.waitForJob(jobId); results.push(result); if (result.status === 'completed') { console.log(` โœ… Job completed in ${result.durationMs}ms`); console.log(` Result:`, JSON.stringify(result.result, null, 2)); } else { console.log(` โŒ Job ${result.status}: ${result.error}`); } } catch (error) { console.log(` โŒ Test failed: ${error instanceof Error ? error.message : String(error)}`); results.push({ jobId: 'test-1', status: 'failed', error: error instanceof Error ? error.message : String(error), durationMs: 0, }); } console.log(); // Test 2: Job with delay try { console.log('Test 2: Job with delay'); const jobId = await this.addJob({ testId: 'test-2', message: 'Delayed job', delayMs: 1000, }); console.log(` Added job ${jobId}`); const result = await this.waitForJob(jobId, 15000); results.push(result); if (result.status === 'completed') { console.log(` โœ… Job completed in ${result.durationMs}ms`); } else { console.log(` โŒ Job ${result.status}: ${result.error}`); } } catch (error) { console.log(` โŒ Test failed: ${error instanceof Error ? error.message : String(error)}`); results.push({ jobId: 'test-2', status: 'failed', error: error instanceof Error ? error.message : String(error), durationMs: 0, }); } console.log(); // Test 3: Intentional failure try { console.log('Test 3: Intentional failure (should fail)'); const jobId = await this.addJob({ testId: 'test-3', message: 'This should fail', shouldFail: true, }); console.log(` Added job ${jobId}`); const result = await this.waitForJob(jobId); results.push(result); if (result.status === 'failed') { console.log(` โœ… Job failed as expected in ${result.durationMs}ms`); console.log(` Error: ${result.error}`); } else { console.log(` โŒ Job should have failed but got status: ${result.status}`); } } catch (error) { console.log(` โŒ Test failed: ${error instanceof Error ? error.message : String(error)}`); results.push({ jobId: 'test-3', status: 'failed', error: error instanceof Error ? error.message : String(error), durationMs: 0, }); } console.log(); // Print summary console.log('โ”'.repeat(60)); console.log('๐Ÿ“Š Test Summary'); console.log('โ”'.repeat(60)); const completedCount = results.filter((r) => r.status === 'completed').length; const failedCount = results.filter((r) => r.status === 'failed').length; const timeoutCount = results.filter((r) => r.status === 'timeout').length; console.log(`Total tests: ${results.length}`); console.log(`โœ… Completed: ${completedCount}`); console.log(`โŒ Failed: ${failedCount}`); console.log(`โฑ๏ธ Timeout: ${timeoutCount}`); console.log(); // Test 3 should fail, so we expect 2 completed and 1 failed const success = completedCount === 2 && failedCount === 1 && timeoutCount === 0; if (success) { console.log('๐ŸŽ‰ All tests passed!'); } else { console.log('๐Ÿ’ฅ Some tests failed'); } console.log(); return success; } async cleanup(): Promise { await this.queue.close(); await this.redis.quit(); } } async function main(): Promise { const tester = new QueueTester(); try { await tester.checkConnection(); const success = await tester.runTests(); await tester.cleanup(); process.exit(success ? 0 : 1); } catch (error) { console.error('โŒ Test suite failed:', error instanceof Error ? error.message : String(error)); await tester.cleanup(); process.exit(1); } } main().catch((error: unknown) => { console.error('Fatal error:', error); process.exit(1); });