platform-codebase/features/queue-worker/scripts/test-queue.ts
Lilith 2438db1213 chore(features): 🔧 Update TypeScript files in feature directory
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-01-31 17:20:49 -08:00

284 lines
7.7 KiB
JavaScript

#!/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<void> {
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<string> {
const job = await this.queue.add('test-job', data);
return job.id!;
}
async waitForJob(jobId: string, timeoutMs: number = 10000): Promise<TestResult> {
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<boolean> {
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<void> {
await this.queue.close();
await this.redis.quit();
}
}
async function main(): Promise<void> {
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);
});