redis_mock = $this->createMock(\Redis::class); // Load QueueProcessor require_once 'modules/desk_moloni/libraries/QueueProcessor.php'; $this->queue_processor = new QueueProcessor(); // Inject Redis mock if possible $reflection = new ReflectionClass($this->queue_processor); if ($reflection->hasProperty('redis')) { $redis_property = $reflection->getProperty('redis'); $redis_property->setAccessible(true); $redis_property->setValue($this->queue_processor, $this->redis_mock); } } #[Test] #[Group('unit')] public function testQueueProcessorInitialization(): void { $this->assertInstanceOf(QueueProcessor::class, $this->queue_processor); // Test priority constants $this->assertEquals(1, QueueProcessor::PRIORITY_LOW); $this->assertEquals(2, QueueProcessor::PRIORITY_NORMAL); $this->assertEquals(3, QueueProcessor::PRIORITY_HIGH); $this->assertEquals(4, QueueProcessor::PRIORITY_CRITICAL); } #[Test] #[Group('unit')] public function testAddJobToQueue(): void { $job_data = [ 'entity_type' => 'customer', 'entity_id' => 123, 'action' => 'create', 'direction' => 'perfex_to_moloni', 'priority' => QueueProcessor::PRIORITY_NORMAL, 'payload' => ['test_data' => 'value'] ]; // Mock Redis operations $this->redis_mock ->expects($this->once()) ->method('zadd') ->willReturn(1); $this->redis_mock ->expects($this->once()) ->method('hset') ->willReturn(1); $job_id = $this->queue_processor->add_to_queue( $job_data['entity_type'], $job_data['entity_id'], $job_data['action'], $job_data['direction'], $job_data['priority'], $job_data['payload'] ); $this->assertIsString($job_id); $this->assertNotEmpty($job_id); } #[Test] #[Group('unit')] #[DataProvider('priorityProvider')] public function testQueuePriorityHandling(int $priority, string $expected_queue): void { $job_data = [ 'entity_type' => 'customer', 'entity_id' => 123, 'action' => 'create', 'direction' => 'perfex_to_moloni', 'priority' => $priority, 'payload' => [] ]; // Mock Redis to capture which queue is used $this->redis_mock ->expects($this->once()) ->method('zadd') ->with($expected_queue, $this->anything(), $this->anything()) ->willReturn(1); $this->redis_mock ->expects($this->once()) ->method('hset') ->willReturn(1); $job_id = $this->queue_processor->add_to_queue( $job_data['entity_type'], $job_data['entity_id'], $job_data['action'], $job_data['direction'], $job_data['priority'], $job_data['payload'] ); $this->assertNotFalse($job_id); } public static function priorityProvider(): array { return [ 'Low Priority' => [QueueProcessor::PRIORITY_LOW, 'desk_moloni:queue:main'], 'Normal Priority' => [QueueProcessor::PRIORITY_NORMAL, 'desk_moloni:queue:main'], 'High Priority' => [QueueProcessor::PRIORITY_HIGH, 'desk_moloni:queue:priority'], 'Critical Priority' => [QueueProcessor::PRIORITY_CRITICAL, 'desk_moloni:queue:priority'] ]; } #[Test] #[Group('unit')] public function testProcessSingleJob(): void { $job_id = 'test_job_123'; $job_data = [ 'id' => $job_id, 'entity_type' => 'customer', 'entity_id' => 456, 'action' => 'create', 'direction' => 'perfex_to_moloni', 'payload' => ['company' => 'Test Company'], 'attempts' => 0, 'max_attempts' => 3, 'created_at' => time() ]; // Mock Redis operations for job retrieval $this->redis_mock ->expects($this->once()) ->method('zpopmin') ->willReturn([$job_id => time()]); $this->redis_mock ->expects($this->once()) ->method('hget') ->with('desk_moloni:jobs', $job_id) ->willReturn(json_encode($job_data)); // Mock successful job processing $this->redis_mock ->expects($this->once()) ->method('hdel') ->with('desk_moloni:jobs', $job_id) ->willReturn(1); $result = $this->queue_processor->process_queue(1, 30); $this->assertEquals(1, $result['processed']); $this->assertEquals(1, $result['success']); $this->assertEquals(0, $result['errors']); } #[Test] #[Group('unit')] public function testJobRetryMechanism(): void { $job_id = 'retry_test_job'; $job_data = [ 'id' => $job_id, 'entity_type' => 'customer', 'entity_id' => 789, 'action' => 'create', 'direction' => 'perfex_to_moloni', 'payload' => [], 'attempts' => 1, 'max_attempts' => 3, 'created_at' => time() ]; // Mock job failure that should trigger retry $this->redis_mock ->expects($this->once()) ->method('zpopmin') ->willReturn([$job_id => time()]); $this->redis_mock ->expects($this->once()) ->method('hget') ->willReturn(json_encode($job_data)); // Mock retry scheduling $this->redis_mock ->expects($this->once()) ->method('zadd') ->with('desk_moloni:queue:delayed', $this->anything(), $job_id) ->willReturn(1); $this->redis_mock ->expects($this->once()) ->method('hset') ->willReturn(1); // Simulate job processing with failure $result = $this->queue_processor->process_queue(1, 30); $this->assertEquals(1, $result['processed']); $this->assertEquals(0, $result['success']); $this->assertEquals(1, $result['errors']); } #[Test] #[Group('unit')] public function testJobMaxRetriesExceeded(): void { $job_id = 'max_retries_job'; $job_data = [ 'id' => $job_id, 'entity_type' => 'customer', 'entity_id' => 999, 'action' => 'create', 'direction' => 'perfex_to_moloni', 'payload' => [], 'attempts' => 3, 'max_attempts' => 3, 'created_at' => time() ]; // Mock job that has exceeded max retries $this->redis_mock ->expects($this->once()) ->method('zpopmin') ->willReturn([$job_id => time()]); $this->redis_mock ->expects($this->once()) ->method('hget') ->willReturn(json_encode($job_data)); // Should move to dead letter queue $this->redis_mock ->expects($this->once()) ->method('zadd') ->with('desk_moloni:queue:dead_letter', $this->anything(), $job_id) ->willReturn(1); $result = $this->queue_processor->process_queue(1, 30); $this->assertEquals(1, $result['processed']); $this->assertEquals(0, $result['success']); $this->assertEquals(1, $result['errors']); } #[Test] #[Group('unit')] public function testQueueStatistics(): void { // Mock Redis responses for statistics $this->redis_mock ->expects($this->exactly(5)) ->method('zcard') ->willReturnOnConsecutiveCalls(10, 5, 2, 1, 3); // main, priority, delayed, processing, dead_letter $this->redis_mock ->expects($this->once()) ->method('hlen') ->willReturn(21); // total jobs $this->redis_mock ->expects($this->exactly(3)) ->method('get') ->willReturnOnConsecutiveCalls('100', '95', '5'); // total_processed, total_success, total_errors $stats = $this->queue_processor->get_queue_statistics(); $this->assertIsArray($stats); $this->assertArrayHasKey('pending_main', $stats); $this->assertArrayHasKey('pending_priority', $stats); $this->assertArrayHasKey('delayed', $stats); $this->assertArrayHasKey('processing', $stats); $this->assertArrayHasKey('dead_letter', $stats); $this->assertArrayHasKey('total_queued', $stats); $this->assertArrayHasKey('total_processed', $stats); $this->assertArrayHasKey('total_success', $stats); $this->assertArrayHasKey('total_errors', $stats); $this->assertArrayHasKey('success_rate', $stats); $this->assertEquals(10, $stats['pending_main']); $this->assertEquals(5, $stats['pending_priority']); $this->assertEquals(95.0, $stats['success_rate']); } #[Test] #[Group('unit')] public function testHealthCheck(): void { // Mock Redis connection test $this->redis_mock ->expects($this->once()) ->method('ping') ->willReturn('+PONG'); // Mock queue counts $this->redis_mock ->expects($this->exactly(2)) ->method('zcard') ->willReturnOnConsecutiveCalls(0, 1); // dead_letter, processing $health = $this->queue_processor->health_check(); $this->assertIsArray($health); $this->assertArrayHasKey('status', $health); $this->assertArrayHasKey('checks', $health); $this->assertArrayHasKey('redis', $health['checks']); $this->assertArrayHasKey('dead_letter', $health['checks']); $this->assertArrayHasKey('processing', $health['checks']); $this->assertArrayHasKey('memory', $health['checks']); $this->assertEquals('healthy', $health['status']); $this->assertTrue($health['checks']['redis']['status']); } #[Test] #[Group('unit')] public function testClearAllQueues(): void { // Mock Redis operations for clearing queues $this->redis_mock ->expects($this->exactly(5)) ->method('del') ->willReturn(1); $this->redis_mock ->expects($this->once()) ->method('flushdb') ->willReturn(true); $result = $this->queue_processor->clear_all_queues(); $this->assertTrue($result); } #[Test] #[Group('unit')] public function testJobValidation(): void { // Test invalid entity type $result = $this->queue_processor->add_to_queue( 'invalid_entity', 123, 'create', 'perfex_to_moloni', QueueProcessor::PRIORITY_NORMAL ); $this->assertFalse($result); // Test invalid action $result = $this->queue_processor->add_to_queue( 'customer', 123, 'invalid_action', 'perfex_to_moloni', QueueProcessor::PRIORITY_NORMAL ); $this->assertFalse($result); // Test invalid direction $result = $this->queue_processor->add_to_queue( 'customer', 123, 'create', 'invalid_direction', QueueProcessor::PRIORITY_NORMAL ); $this->assertFalse($result); } #[Test] #[Group('unit')] public function testBatchJobProcessing(): void { $batch_size = 5; $job_ids = []; // Mock multiple jobs in queue for ($i = 0; $i < $batch_size; $i++) { $job_ids[] = "batch_job_{$i}"; } // Mock Redis returning batch of jobs $this->redis_mock ->expects($this->once()) ->method('zpopmin') ->willReturn(array_combine($job_ids, array_fill(0, $batch_size, time()))); // Mock job data retrieval $this->redis_mock ->expects($this->exactly($batch_size)) ->method('hget') ->willReturnCallback(function($key, $job_id) { return json_encode([ 'id' => $job_id, 'entity_type' => 'customer', 'entity_id' => rand(100, 999), 'action' => 'create', 'direction' => 'perfex_to_moloni', 'payload' => [], 'attempts' => 0, 'max_attempts' => 3 ]); }); // Mock successful processing $this->redis_mock ->expects($this->exactly($batch_size)) ->method('hdel') ->willReturn(1); $result = $this->queue_processor->process_queue($batch_size, 60); $this->assertEquals($batch_size, $result['processed']); $this->assertEquals($batch_size, $result['success']); $this->assertEquals(0, $result['errors']); } #[Test] #[Group('unit')] public function testJobTimeout(): void { $timeout = 1; // 1 second timeout for testing $job_data = [ 'id' => 'timeout_job', 'entity_type' => 'customer', 'entity_id' => 123, 'action' => 'create', 'direction' => 'perfex_to_moloni', 'payload' => [], 'attempts' => 0, 'max_attempts' => 3 ]; // Mock job retrieval $this->redis_mock ->expects($this->once()) ->method('zpopmin') ->willReturn(['timeout_job' => time()]); $this->redis_mock ->expects($this->once()) ->method('hget') ->willReturn(json_encode($job_data)); // Process with very short timeout $start_time = microtime(true); $result = $this->queue_processor->process_queue(1, $timeout); $execution_time = microtime(true) - $start_time; // Should respect timeout $this->assertLessThanOrEqual($timeout + 0.5, $execution_time); // Allow small margin } protected function tearDown(): void { $this->queue_processor = null; $this->redis_mock = null; parent::tearDown(); } }