container = createTestContainer(); // Get services from container $this->queue = $this->container->get(QueueInterface::class); $this->dependencyManager = $this->container->get(JobDependencyManagerInterface::class); $this->chainManager = $this->container->get(JobChainManagerInterface::class); $this->metricsManager = $this->container->get(JobMetricsManager::class); $this->resolutionEngine = $this->container->get(DependencyResolutionEngine::class); $this->chainCoordinator = $this->container->get(JobChainExecutionCoordinator::class); $this->entityManager = $this->container->get(EntityManagerInterface::class); $this->logger = $this->container->get(Logger::class); }); function createTestJob(string $id, string $data): object { return new class ($id, $data) { public function __construct( public readonly string $id, public readonly string $data ) { } }; } test('complete queue workflow with dependencies and metrics', function () { // 1. Create test jobs $job1 = createTestJob('job-1', 'Test Job 1'); $job2 = createTestJob('job-2', 'Test Job 2'); $job3 = createTestJob('job-3', 'Test Job 3'); // 2. Set up dependencies: job2 depends on job1, job3 depends on job2 $dependency1 = JobDependency::completion('job-2', 'job-1'); $dependency2 = JobDependency::success('job-3', 'job-2'); // Add dependencies $this->dependencyManager->addDependency($dependency1); $this->dependencyManager->addDependency($dependency2); // 3. Add jobs to queue $this->queue->push($job1); $this->queue->push($job2); $this->queue->push($job3); // 4. Create and record metrics for job execution $job1Metrics = new JobMetrics( jobId: 'job-1', queueName: 'default', status: 'completed', attempts: 1, maxAttempts: 3, executionTimeMs: 150.5, memoryUsageBytes: 1024 * 1024, errorMessage: null, createdAt: date('Y-m-d H:i:s'), startedAt: date('Y-m-d H:i:s'), completedAt: date('Y-m-d H:i:s'), failedAt: null, metadata: ['test' => true] ); $this->metricsManager->recordJobMetrics($job1Metrics); // 5. Test dependency resolution $readyJobs = $this->resolutionEngine->getJobsReadyForExecution(); expect($readyJobs)->toHaveCount(1) ->and($readyJobs[0])->toBe('job-1'); // 6. Mark job1 as completed and check dependencies $this->dependencyManager->markJobCompleted('job-1'); $readyJobsAfterJob1 = $this->resolutionEngine->getJobsReadyForExecution(); expect($readyJobsAfterJob1)->toContain('job-2'); // 7. Test metrics retrieval $retrievedMetrics = $this->metricsManager->getJobMetrics('job-1'); expect($retrievedMetrics)->not()->toBeNull() ->and($retrievedMetrics->jobId)->toBe('job-1') ->and($retrievedMetrics->status)->toBe('completed') ->and($retrievedMetrics->executionTimeMs)->toBe(150.5); // 8. Test queue metrics calculation $queueMetrics = $this->metricsManager->getQueueMetrics('default', '1 hour'); expect($queueMetrics->queueName)->toBe('default') ->and($queueMetrics->totalJobs)->toBeGreaterThan(0); }); test('job chain execution with sequential mode', function () { // 1. Create jobs for chain $jobs = [ createTestJob('chain-job-1', 'Chain Job 1'), createTestJob('chain-job-2', 'Chain Job 2'), createTestJob('chain-job-3', 'Chain Job 3'), ]; // 2. Create job chain $chain = JobChain::sequential('test-chain', ['chain-job-1', 'chain-job-2', 'chain-job-3']); // 3. Add chain to manager $this->chainManager->createChain($chain); // 4. Execute chain $this->chainCoordinator->executeChain('test-chain'); // 5. Verify chain was created $retrievedChain = $this->chainManager->getChain('test-chain'); expect($retrievedChain)->not()->toBeNull() ->and($retrievedChain->name)->toBe('test-chain') ->and($retrievedChain->executionMode)->toBe(ChainExecutionMode::SEQUENTIAL) ->and($retrievedChain->jobIds)->toHaveCount(3); }); test('job chain failure handling', function () { // 1. Create jobs for chain with one that will fail $jobs = [ createTestJob('fail-job-1', 'Job 1'), createTestJob('fail-job-2', 'Job 2 (will fail)'), createTestJob('fail-job-3', 'Job 3'), ]; // 2. Create job chain with stop on failure $chain = JobChain::sequential('fail-chain', ['fail-job-1', 'fail-job-2', 'fail-job-3']); $this->chainManager->createChain($chain); // 3. Simulate job failure $failureMetrics = new JobMetrics( jobId: 'fail-job-2', queueName: 'default', status: 'failed', attempts: 3, maxAttempts: 3, executionTimeMs: 50.0, memoryUsageBytes: 512 * 1024, errorMessage: 'Simulated failure', createdAt: date('Y-m-d H:i:s'), startedAt: date('Y-m-d H:i:s'), completedAt: null, failedAt: date('Y-m-d H:i:s'), metadata: [] ); $this->metricsManager->recordJobMetrics($failureMetrics); // 4. Test failure detection $failedJobs = $this->metricsManager->getFailedJobs('default', '1 hour'); expect($failedJobs)->toHaveCount(1) ->and($failedJobs[0]->jobId)->toBe('fail-job-2') ->and($failedJobs[0]->status)->toBe('failed'); }); test('circular dependency detection', function () { // 1. Create circular dependencies: A depends on B, B depends on C, C depends on A $depA = JobDependency::completion('job-a', 'job-b'); $depB = JobDependency::completion('job-b', 'job-c'); $depC = JobDependency::completion('job-c', 'job-a'); // 2. Add dependencies $this->dependencyManager->addDependency($depA); $this->dependencyManager->addDependency($depB); // 3. Adding the third dependency should throw an exception or be handled expect(fn () => $this->dependencyManager->addDependency($depC)) ->toThrow(\InvalidArgumentException::class); }); test('conditional dependencies', function () { // 1. Create jobs $job1 = createTestJob('cond-job-1', 'Conditional Job 1'); $job2 = createTestJob('cond-job-2', 'Conditional Job 2'); // 2. Create success-based dependency $successDep = JobDependency::success('cond-job-2', 'cond-job-1'); $this->dependencyManager->addDependency($successDep); // 3. Test that job2 is not ready when job1 failed $failureMetrics = new JobMetrics( jobId: 'cond-job-1', queueName: 'default', status: 'failed', attempts: 3, maxAttempts: 3, executionTimeMs: 100.0, memoryUsageBytes: 1024, errorMessage: 'Test failure', createdAt: date('Y-m-d H:i:s'), startedAt: date('Y-m-d H:i:s'), completedAt: null, failedAt: date('Y-m-d H:i:s'), metadata: [] ); $this->metricsManager->recordJobMetrics($failureMetrics); // 4. Check that dependent job is not ready $readyJobs = $this->resolutionEngine->getJobsReadyForExecution(); expect($readyJobs)->not()->toContain('cond-job-2'); }); test('queue metrics calculation', function () { // 1. Create multiple job metrics $metrics = [ new JobMetrics( jobId: 'metric-job-1', queueName: 'metrics-queue', status: 'completed', attempts: 1, maxAttempts: 3, executionTimeMs: 100.0, memoryUsageBytes: 1024 * 1024, errorMessage: null, createdAt: date('Y-m-d H:i:s'), startedAt: date('Y-m-d H:i:s'), completedAt: date('Y-m-d H:i:s'), failedAt: null, metadata: [] ), new JobMetrics( jobId: 'metric-job-2', queueName: 'metrics-queue', status: 'completed', attempts: 1, maxAttempts: 3, executionTimeMs: 200.0, memoryUsageBytes: 2 * 1024 * 1024, errorMessage: null, createdAt: date('Y-m-d H:i:s'), startedAt: date('Y-m-d H:i:s'), completedAt: date('Y-m-d H:i:s'), failedAt: null, metadata: [] ), new JobMetrics( jobId: 'metric-job-3', queueName: 'metrics-queue', status: 'failed', attempts: 3, maxAttempts: 3, executionTimeMs: 50.0, memoryUsageBytes: 512 * 1024, errorMessage: 'Test failure', createdAt: date('Y-m-d H:i:s'), startedAt: date('Y-m-d H:i:s'), completedAt: null, failedAt: date('Y-m-d H:i:s'), metadata: [] ), ]; // 2. Record all metrics foreach ($metrics as $metric) { $this->metricsManager->recordJobMetrics($metric); } // 3. Calculate queue metrics $queueMetrics = $this->metricsManager->getQueueMetrics('metrics-queue', '1 hour'); // 4. Verify calculations expect($queueMetrics->queueName)->toBe('metrics-queue') ->and($queueMetrics->totalJobs)->toBe(3) ->and($queueMetrics->completedJobs)->toBe(2) ->and($queueMetrics->failedJobs)->toBe(1) ->and($queueMetrics->successRate->toFloat())->toBe(66.67); }); test('dead letter queue functionality', function () { // 1. Create a job that exceeds max attempts $deadLetterMetrics = new JobMetrics( jobId: 'dead-letter-job', queueName: 'default', status: 'failed', attempts: 3, maxAttempts: 3, executionTimeMs: 25.0, memoryUsageBytes: 256 * 1024, errorMessage: 'Max attempts exceeded', createdAt: date('Y-m-d H:i:s'), startedAt: date('Y-m-d H:i:s'), completedAt: null, failedAt: date('Y-m-d H:i:s'), metadata: ['dead_letter' => true] ); // 2. Record metrics $this->metricsManager->recordJobMetrics($deadLetterMetrics); // 3. Verify dead letter detection $failedJobs = $this->metricsManager->getFailedJobs('default', '1 hour'); $deadLetterJob = array_filter($failedJobs, fn ($job) => $job->jobId === 'dead-letter-job')[0] ?? null; expect($deadLetterJob)->not()->toBeNull() ->and($deadLetterJob->attempts)->toBe(3) ->and($deadLetterJob->maxAttempts)->toBe(3) ->and($deadLetterJob->status)->toBe('failed'); }); test('system health monitoring', function () { // 1. Create mixed job metrics for health calculation $healthMetrics = [ // Healthy jobs new JobMetrics('health-1', 'health-queue', 'completed', 1, 3, 50.0, 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []), new JobMetrics('health-2', 'health-queue', 'completed', 1, 3, 75.0, 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []), new JobMetrics('health-3', 'health-queue', 'completed', 1, 3, 100.0, 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []), // One failed job new JobMetrics('health-4', 'health-queue', 'failed', 2, 3, 25.0, 1024, 'Health test failure', date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, date('Y-m-d H:i:s'), []), ]; // 2. Record all metrics foreach ($healthMetrics as $metric) { $this->metricsManager->recordJobMetrics($metric); } // 3. Get system overview $overview = $this->metricsManager->getSystemOverview(); // 4. Verify system health calculation expect($overview)->toHaveKey('system_health_score') ->and($overview['total_jobs'])->toBeGreaterThan(0) ->and($overview['overall_success_rate'])->toBeGreaterThan(0); }); test('performance and throughput metrics', function () { // 1. Create performance test metrics with varying execution times $performanceMetrics = [ new JobMetrics('perf-1', 'perf-queue', 'completed', 1, 3, 50.0, 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []), new JobMetrics('perf-2', 'perf-queue', 'completed', 1, 3, 150.0, 2 * 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []), new JobMetrics('perf-3', 'perf-queue', 'completed', 1, 3, 300.0, 4 * 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []), ]; // 2. Record performance metrics foreach ($performanceMetrics as $metric) { $this->metricsManager->recordJobMetrics($metric); } // 3. Get performance statistics $performanceStats = $this->metricsManager->getPerformanceStats('perf-queue', '1 hour'); // 4. Verify performance calculations expect($performanceStats)->toHaveKey('average_execution_time_ms') ->and($performanceStats['average_execution_time_ms'])->toBe(166.67) ->and($performanceStats)->toHaveKey('average_memory_usage_mb') ->and($performanceStats['total_jobs'])->toBe(3); // 5. Get throughput statistics $throughputStats = $this->metricsManager->getThroughputStats('perf-queue', '1 hour'); // 6. Verify throughput calculations expect($throughputStats)->toHaveKey('total_completed') ->and($throughputStats['total_completed'])->toBe(3) ->and($throughputStats)->toHaveKey('average_throughput_per_hour'); });