container = new DefaultContainer(); // Mock essential framework dependencies $this->mockEntityManager = new class () implements EntityManagerInterface { public function persist(object $entity): void { } public function find(string $className, mixed $id): ?object { return null; } public function flush(): void { } public function remove(object $entity): void { } public function clear(): void { } public function detach(object $entity): void { } public function contains(object $entity): bool { return false; } public function refresh(object $entity): void { } public function createQueryBuilder(): object { return new stdClass(); } public function getRepository(string $className): object { return new stdClass(); } public function beginTransaction(): void { } public function commit(): void { } public function rollback(): void { } public function isTransactionActive(): bool { return false; } }; $this->mockLogger = new class () implements Logger { public function emergency(string $message, array $context = []): void { } public function alert(string $message, array $context = []): void { } public function critical(string $message, array $context = []): void { } public function error(string $message, array $context = []): void { } public function warning(string $message, array $context = []): void { } public function notice(string $message, array $context = []): void { } public function info(string $message, array $context = []): void { } public function debug(string $message, array $context = []): void { } public function log(string $level, string $message, array $context = []): void { } }; // Register mocked dependencies $this->container->instance(EntityManagerInterface::class, $this->mockEntityManager); $this->container->instance(Logger::class, $this->mockLogger); }); describe('Core Queue Service', function () { it('registers Queue service correctly', function () { // This test verifies that the QueueInitializer properly registers a Queue // Note: This will fallback to FileQueue since Redis is not available in tests $queueInitializer = new QueueInitializer( pathProvider: new class () { public function resolvePath(string $path): string { return '/home/michael/dev/michaelschiemer/tests/tmp/queue/'; } } ); $queue = $queueInitializer($this->mockLogger); expect($queue)->toBeInstanceOf(Queue::class); expect($queue)->not->toBeNull(); }); it('Queue service is accessible from container after registration', function () { // Register queue manually for testing $this->container->singleton(Queue::class, function () { return new \App\Framework\Queue\InMemoryQueue(); }); $queue = $this->container->get(Queue::class); expect($queue)->toBeInstanceOf(Queue::class); }); }); describe('Queue Dependencies Registration', function () { beforeEach(function () { // Initialize the queue dependency system $this->dependencyInitializer = new QueueDependencyInitializer(); // Register a basic queue interface for the dependencies $this->container->singleton(\App\Framework\Queue\Contracts\QueueInterface::class, function () { return new class () implements \App\Framework\Queue\Contracts\QueueInterface { public function push(mixed $job): void { } public function pop(): mixed { return null; } public function size(): int { return 0; } }; }); // Register EventDispatcher mock $this->container->singleton(\App\Framework\Core\Events\EventDispatcherInterface::class, function () { return new class () implements \App\Framework\Core\Events\EventDispatcherInterface { public function dispatch(object $event): void { } public function listen(string $event, callable $listener): void { } }; }); }); it('registers JobDependencyManagerInterface', function () { $dependencyManager = $this->dependencyInitializer->__invoke($this->container); expect($dependencyManager)->toBeInstanceOf(JobDependencyManagerInterface::class); expect($dependencyManager)->toBeInstanceOf(DatabaseJobDependencyManager::class); // Should be accessible from container $retrieved = $this->container->get(JobDependencyManagerInterface::class); expect($retrieved)->toBe($dependencyManager); }); it('registers JobChainManagerInterface', function () { $this->dependencyInitializer->__invoke($this->container); $chainManager = $this->container->get(JobChainManagerInterface::class); expect($chainManager)->toBeInstanceOf(JobChainManagerInterface::class); expect($chainManager)->toBeInstanceOf(DatabaseJobChainManager::class); }); it('registers DependencyResolutionEngine as singleton', function () { $this->dependencyInitializer->__invoke($this->container); $engine1 = $this->container->get(DependencyResolutionEngine::class); $engine2 = $this->container->get(DependencyResolutionEngine::class); expect($engine1)->toBeInstanceOf(DependencyResolutionEngine::class); expect($engine1)->toBe($engine2); // Should be same instance (singleton) }); it('registers JobMetricsManager as singleton', function () { $this->dependencyInitializer->__invoke($this->container); $metrics1 = $this->container->get(JobMetricsManager::class); $metrics2 = $this->container->get(JobMetricsManager::class); expect($metrics1)->toBeInstanceOf(JobMetricsManager::class); expect($metrics1)->toBe($metrics2); // Should be same instance (singleton) }); }); describe('Individual Service Registration', function () { it('can register DistributedLockInterface service', function () { $lockService = new DatabaseDistributedLock( entityManager: $this->mockEntityManager, logger: $this->mockLogger ); $this->container->singleton(DistributedLockInterface::class, $lockService); $retrieved = $this->container->get(DistributedLockInterface::class); expect($retrieved)->toBe($lockService); expect($retrieved)->toBeInstanceOf(DistributedLockInterface::class); }); it('can register JobProgressTrackerInterface service', function () { $progressTracker = new DatabaseJobProgressTracker( entityManager: $this->mockEntityManager, logger: $this->mockLogger ); $this->container->singleton(JobProgressTrackerInterface::class, $progressTracker); $retrieved = $this->container->get(JobProgressTrackerInterface::class); expect($retrieved)->toBe($progressTracker); expect($retrieved)->toBeInstanceOf(JobProgressTrackerInterface::class); }); it('can register DeadLetterQueueInterface service', function () { $deadLetterQueue = new DatabaseDeadLetterQueue( entityManager: $this->mockEntityManager, logger: $this->mockLogger ); $this->container->singleton(DeadLetterQueueInterface::class, $deadLetterQueue); $retrieved = $this->container->get(DeadLetterQueueInterface::class); expect($retrieved)->toBe($deadLetterQueue); expect($retrieved)->toBeInstanceOf(DeadLetterQueueInterface::class); }); it('can register WorkerRegistry service', function () { $workerRegistry = new WorkerRegistry( entityManager: $this->mockEntityManager, logger: $this->mockLogger ); $this->container->singleton(WorkerRegistry::class, $workerRegistry); $retrieved = $this->container->get(WorkerRegistry::class); expect($retrieved)->toBe($workerRegistry); expect($retrieved)->toBeInstanceOf(WorkerRegistry::class); }); it('can register JobDistributionService', function () { // First register dependencies $this->container->singleton(WorkerRegistry::class, new WorkerRegistry( $this->mockEntityManager, $this->mockLogger )); $this->container->singleton(\App\Framework\Queue\Contracts\QueueInterface::class, function () { return new class () implements \App\Framework\Queue\Contracts\QueueInterface { public function push(mixed $job): void { } public function pop(): mixed { return null; } public function size(): int { return 0; } }; }); $distributionService = new JobDistributionService( workerRegistry: $this->container->get(WorkerRegistry::class), queue: $this->container->get(\App\Framework\Queue\Contracts\QueueInterface::class), logger: $this->mockLogger ); $this->container->singleton(JobDistributionService::class, $distributionService); $retrieved = $this->container->get(JobDistributionService::class); expect($retrieved)->toBe($distributionService); expect($retrieved)->toBeInstanceOf(JobDistributionService::class); }); }); describe('Service Dependencies and Integration', function () { it('services have proper dependencies injected', function () { $this->dependencyInitializer->__invoke($this->container); $dependencyManager = $this->container->get(JobDependencyManagerInterface::class); $chainManager = $this->container->get(JobChainManagerInterface::class); $resolutionEngine = $this->container->get(DependencyResolutionEngine::class); // Verify dependencies are properly injected expect($dependencyManager)->toBeInstanceOf(DatabaseJobDependencyManager::class); expect($chainManager)->toBeInstanceOf(DatabaseJobChainManager::class); expect($resolutionEngine)->toBeInstanceOf(DependencyResolutionEngine::class); // These services should be functional (not throw errors) expect(fn () => $dependencyManager)->not->toThrow(); expect(fn () => $chainManager)->not->toThrow(); expect(fn () => $resolutionEngine)->not->toThrow(); }); it('can resolve complex dependency graph', function () { $this->dependencyInitializer->__invoke($this->container); // Add additional services $this->container->singleton(DistributedLockInterface::class, function () { return new DatabaseDistributedLock( $this->mockEntityManager, $this->mockLogger ); }); $this->container->singleton(JobProgressTrackerInterface::class, function () { return new DatabaseJobProgressTracker( $this->mockEntityManager, $this->mockLogger ); }); // All services should be resolvable $services = [ JobDependencyManagerInterface::class, JobChainManagerInterface::class, DependencyResolutionEngine::class, JobMetricsManager::class, DistributedLockInterface::class, JobProgressTrackerInterface::class, ]; foreach ($services as $serviceInterface) { $service = $this->container->get($serviceInterface); expect($service)->not->toBeNull(); expect($service)->toBeObject(); } }); }); describe('Service Lifecycle Management', function () { it('singleton services maintain state across requests', function () { $this->dependencyInitializer->__invoke($this->container); $metrics1 = $this->container->get(JobMetricsManager::class); $metrics2 = $this->container->get(JobMetricsManager::class); // Should be exact same instance expect($metrics1)->toBe($metrics2); }); it('services can be replaced for testing', function () { $this->dependencyInitializer->__invoke($this->container); // Get original service $original = $this->container->get(JobMetricsManager::class); // Create mock replacement $mock = new class () implements JobMetricsManagerInterface { public function recordJobExecution(\App\Framework\Queue\ValueObjects\JobId $jobId, float $executionTime): void { } public function recordJobFailure(\App\Framework\Queue\ValueObjects\JobId $jobId, string $errorMessage): void { } public function getJobMetrics(\App\Framework\Queue\ValueObjects\JobId $jobId): ?\App\Framework\Queue\ValueObjects\JobMetrics { return null; } public function getQueueMetrics(\App\Framework\Queue\ValueObjects\QueueName $queueName): \App\Framework\Queue\ValueObjects\QueueMetrics { return new \App\Framework\Queue\ValueObjects\QueueMetrics( queueName: $queueName, totalJobs: 0, completedJobs: 0, failedJobs: 0, averageExecutionTime: 0.0 ); } public function getSystemMetrics(): array { return []; } }; // Replace with mock $this->container->instance(JobMetricsManagerInterface::class, $mock); $replaced = $this->container->get(JobMetricsManagerInterface::class); expect($replaced)->toBe($mock); expect($replaced)->not->toBe($original); }); it('handles missing dependencies gracefully', function () { // Don't register EventDispatcher - this should cause failure unset($this->container); $this->container = new DefaultContainer(); $this->container->instance(EntityManagerInterface::class, $this->mockEntityManager); $this->container->instance(Logger::class, $this->mockLogger); $dependencyInitializer = new QueueDependencyInitializer(); // This should fail due to missing dependencies expect(fn () => $dependencyInitializer->__invoke($this->container)) ->toThrow(); }); }); }); describe('Queue Service Integration Test', function () { beforeEach(function () { $this->container = new DefaultContainer(); // Register all required mocks $this->container->instance(EntityManagerInterface::class, new class () implements EntityManagerInterface { public function persist(object $entity): void { } public function find(string $className, mixed $id): ?object { return null; } public function flush(): void { } public function remove(object $entity): void { } public function clear(): void { } public function detach(object $entity): void { } public function contains(object $entity): bool { return false; } public function refresh(object $entity): void { } public function createQueryBuilder(): object { return new stdClass(); } public function getRepository(string $className): object { return new stdClass(); } public function beginTransaction(): void { } public function commit(): void { } public function rollback(): void { } public function isTransactionActive(): bool { return false; } }); $this->container->instance(Logger::class, new class () implements Logger { public function emergency(string $message, array $context = []): void { } public function alert(string $message, array $context = []): void { } public function critical(string $message, array $context = []): void { } public function error(string $message, array $context = []): void { } public function warning(string $message, array $context = []): void { } public function notice(string $message, array $context = []): void { } public function info(string $message, array $context = []): void { } public function debug(string $message, array $context = []): void { } public function log(string $level, string $message, array $context = []): void { } }); $this->container->singleton(\App\Framework\Queue\Contracts\QueueInterface::class, function () { return new class () implements \App\Framework\Queue\Contracts\QueueInterface { public function push(mixed $job): void { } public function pop(): mixed { return null; } public function size(): int { return 0; } }; }); $this->container->singleton(\App\Framework\Core\Events\EventDispatcherInterface::class, function () { return new class () implements \App\Framework\Core\Events\EventDispatcherInterface { public function dispatch(object $event): void { } public function listen(string $event, callable $listener): void { } }; }); }); it('can initialize complete queue system', function () { // Initialize all queue services $dependencyInitializer = new QueueDependencyInitializer(); $dependencyInitializer->__invoke($this->container); // Register additional services that would normally be auto-registered $this->container->singleton(DistributedLockInterface::class, function ($container) { return new DatabaseDistributedLock( $container->get(EntityManagerInterface::class), $container->get(Logger::class) ); }); $this->container->singleton(JobProgressTrackerInterface::class, function ($container) { return new DatabaseJobProgressTracker( $container->get(EntityManagerInterface::class), $container->get(Logger::class) ); }); $this->container->singleton(DeadLetterQueueInterface::class, function ($container) { return new DatabaseDeadLetterQueue( $container->get(EntityManagerInterface::class), $container->get(Logger::class) ); }); $this->container->singleton(WorkerRegistry::class, function ($container) { return new WorkerRegistry( $container->get(EntityManagerInterface::class), $container->get(Logger::class) ); }); // Verify all 9 expected queue services are registered $expectedServices = [ DistributedLockInterface::class, WorkerRegistry::class, JobDistributionService::class, // This might not be auto-registered WorkerHealthCheckService::class, // This might not be auto-registered FailoverRecoveryService::class, // This might not be auto-registered JobProgressTrackerInterface::class, DeadLetterQueueInterface::class, JobMetricsManagerInterface::class, JobDependencyManagerInterface::class, ]; $registeredCount = 0; foreach ($expectedServices as $service) { try { $instance = $this->container->get($service); if ($instance !== null) { $registeredCount++; } } catch (\Exception $e) { // Service not registered, which is expected for some } } // At least the core services should be registered expect($registeredCount)->toBeGreaterThan(4); }); it('services can interact without errors', function () { $dependencyInitializer = new QueueDependencyInitializer(); $dependencyInitializer->__invoke($this->container); $dependencyManager = $this->container->get(JobDependencyManagerInterface::class); $chainManager = $this->container->get(JobChainManagerInterface::class); $metricsManager = $this->container->get(JobMetricsManager::class); // Basic interaction tests (should not throw) expect(fn () => $dependencyManager)->not->toThrow(); expect(fn () => $chainManager)->not->toThrow(); expect(fn () => $metricsManager)->not->toThrow(); }); });