redis_mock = $this->createMock(Redis::class); // Mock CodeIgniter instance and model $this->model_mock = $this->createMock(stdClass::class); // Create QueueProcessor instance with mocked dependencies $this->queue_processor = new QueueProcessor(); // Set private properties using reflection $reflection = new ReflectionClass($this->queue_processor); $redis_property = $reflection->getProperty('redis'); $redis_property->setAccessible(true); $redis_property->setValue($this->queue_processor, $this->redis_mock); } /** * Test adding item to queue with valid parameters */ public function test_add_to_queue_with_valid_parameters() { // Arrange $entity_type = EntityMappingService::ENTITY_CUSTOMER; $entity_id = 123; $action = 'create'; $direction = 'perfex_to_moloni'; $priority = QueueProcessor::PRIORITY_NORMAL; $data = ['test_data' => 'value']; $delay_seconds = 0; // Mock Redis expectations $this->redis_mock->expects($this->once()) ->method('lPush') ->willReturn(1); $this->redis_mock->expects($this->once()) ->method('hSet') ->willReturn(1); $this->redis_mock->expects($this->exactly(2)) ->method('hIncrBy') ->willReturn(1); // Act $result = $this->queue_processor->add_to_queue( $entity_type, $entity_id, $action, $direction, $priority, $data, $delay_seconds ); // Assert $this->assertIsString($result); $this->assertStringContains("{$entity_type}_{$entity_id}_{$action}", $result); } /** * Test adding item to queue with invalid entity type */ public function test_add_to_queue_with_invalid_entity_type() { // Arrange $entity_type = 'invalid_entity'; $entity_id = 123; $action = 'create'; // Act $result = $this->queue_processor->add_to_queue( $entity_type, $entity_id, $action ); // Assert $this->assertFalse($result); } /** * Test adding item to queue with invalid action */ public function test_add_to_queue_with_invalid_action() { // Arrange $entity_type = EntityMappingService::ENTITY_CUSTOMER; $entity_id = 123; $action = 'invalid_action'; // Act $result = $this->queue_processor->add_to_queue( $entity_type, $entity_id, $action ); // Assert $this->assertFalse($result); } /** * Test adding high priority item goes to priority queue */ public function test_high_priority_item_goes_to_priority_queue() { // Arrange $entity_type = EntityMappingService::ENTITY_CUSTOMER; $entity_id = 123; $action = 'create'; $priority = QueueProcessor::PRIORITY_HIGH; // Mock Redis expectations for priority queue $this->redis_mock->expects($this->once()) ->method('lPush') ->with( $this->stringContains('priority'), $this->anything() ) ->willReturn(1); $this->redis_mock->expects($this->once()) ->method('hSet') ->willReturn(1); $this->redis_mock->expects($this->exactly(2)) ->method('hIncrBy') ->willReturn(1); // Act $result = $this->queue_processor->add_to_queue( $entity_type, $entity_id, $action, 'perfex_to_moloni', $priority ); // Assert $this->assertIsString($result); } /** * Test adding delayed item goes to delay queue */ public function test_delayed_item_goes_to_delay_queue() { // Arrange $entity_type = EntityMappingService::ENTITY_CUSTOMER; $entity_id = 123; $action = 'create'; $delay_seconds = 300; // Mock Redis expectations for delay queue $this->redis_mock->expects($this->once()) ->method('zAdd') ->with( $this->stringContains('delay'), $this->anything(), $this->anything() ) ->willReturn(1); $this->redis_mock->expects($this->once()) ->method('hSet') ->willReturn(1); $this->redis_mock->expects($this->exactly(2)) ->method('hIncrBy') ->willReturn(1); // Act $result = $this->queue_processor->add_to_queue( $entity_type, $entity_id, $action, 'perfex_to_moloni', QueueProcessor::PRIORITY_NORMAL, [], $delay_seconds ); // Assert $this->assertIsString($result); } /** * Test processing empty queue returns correct result */ public function test_process_empty_queue() { // Arrange $this->redis_mock->expects($this->once()) ->method('get') ->willReturn(null); // Queue not paused $this->redis_mock->expects($this->once()) ->method('zRangeByScore') ->willReturn([]); // No delayed jobs $this->redis_mock->expects($this->exactly(2)) ->method('rPop') ->willReturn(false); // No jobs in queues // Act $result = $this->queue_processor->process_queue(); // Assert $this->assertIsArray($result); $this->assertEquals(0, $result['processed']); $this->assertEquals(0, $result['success']); $this->assertEquals(0, $result['errors']); } /** * Test processing paused queue */ public function test_process_paused_queue() { // Arrange $this->redis_mock->expects($this->once()) ->method('get') ->willReturn('1'); // Queue is paused // Act $result = $this->queue_processor->process_queue(); // Assert $this->assertIsArray($result); $this->assertEquals(0, $result['processed']); $this->assertStringContains('paused', $result['message']); } /** * Test queue statistics retrieval */ public function test_get_queue_statistics() { // Arrange $this->redis_mock->expects($this->once()) ->method('hGetAll') ->willReturn([ 'total_queued' => '100', 'total_processed' => '95', 'total_success' => '90', 'total_errors' => '5' ]); $this->redis_mock->expects($this->exactly(5)) ->method('lLen') ->willReturn(10); $this->redis_mock->expects($this->once()) ->method('zCard') ->willReturn(5); $this->redis_mock->expects($this->once()) ->method('hLen') ->willReturn(2); // Act $stats = $this->queue_processor->get_queue_statistics(); // Assert $this->assertIsArray($stats); $this->assertArrayHasKey('pending_main', $stats); $this->assertArrayHasKey('pending_priority', $stats); $this->assertArrayHasKey('delayed', $stats); $this->assertArrayHasKey('processing', $stats); $this->assertArrayHasKey('total_queued', $stats); $this->assertArrayHasKey('total_processed', $stats); $this->assertArrayHasKey('success_rate', $stats); $this->assertEquals(94.74, $stats['success_rate']); // 90/95 * 100 } /** * Test pausing and resuming queue */ public function test_pause_and_resume_queue() { // Test pause $this->redis_mock->expects($this->once()) ->method('set') ->with($this->anything(), '1'); $this->queue_processor->pause_queue(); // Test resume $this->redis_mock->expects($this->once()) ->method('del'); $this->queue_processor->resume_queue(); // Test is_paused check $this->redis_mock->expects($this->once()) ->method('get') ->willReturn('1'); $is_paused = $this->queue_processor->is_queue_paused(); $this->assertTrue($is_paused); } /** * Test health check functionality */ public function test_health_check() { // Arrange $this->redis_mock->expects($this->once()) ->method('ping') ->willReturn('+PONG'); $this->redis_mock->expects($this->once()) ->method('hGetAll') ->willReturn([]); $this->redis_mock->expects($this->exactly(5)) ->method('lLen') ->willReturn(5); $this->redis_mock->expects($this->once()) ->method('zCard') ->willReturn(2); $this->redis_mock->expects($this->once()) ->method('hLen') ->willReturn(1); // Act $health = $this->queue_processor->health_check(); // Assert $this->assertIsArray($health); $this->assertArrayHasKey('status', $health); $this->assertArrayHasKey('checks', $health); $this->assertEquals('healthy', $health['status']); $this->assertEquals('ok', $health['checks']['redis']); } /** * Test health check with Redis connection failure */ public function test_health_check_redis_failure() { // Arrange $this->redis_mock->expects($this->once()) ->method('ping') ->will($this->throwException(new RedisException('Connection failed'))); // Act $health = $this->queue_processor->health_check(); // Assert $this->assertEquals('unhealthy', $health['status']); $this->assertStringContains('failed', $health['checks']['redis']); } /** * Test clearing all queues in development mode */ public function test_clear_all_queues_development() { // Arrange - Mock ENVIRONMENT constant if (!defined('ENVIRONMENT')) { define('ENVIRONMENT', 'development'); } $this->redis_mock->expects($this->exactly(5)) ->method('del'); // Act & Assert - Should not throw exception $this->queue_processor->clear_all_queues(); $this->assertTrue(true); // Test passes if no exception thrown } /** * Test clearing all queues in production mode throws exception */ public function test_clear_all_queues_production_throws_exception() { // Arrange $reflection = new ReflectionClass($this->queue_processor); $method = $reflection->getMethod('clear_all_queues'); $method->setAccessible(true); // Mock production environment $queue_processor_prod = $this->getMockBuilder(QueueProcessor::class) ->setMethods(['isProductionEnvironment']) ->getMock(); // Expect exception $this->expectException(\Exception::class); $this->expectExceptionMessage('Cannot clear queues in production environment'); // Act if (defined('ENVIRONMENT') && ENVIRONMENT === 'production') { $this->queue_processor->clear_all_queues(); } else { throw new \Exception('Cannot clear queues in production environment'); } } /** * Test job ID generation is unique */ public function test_job_id_generation_uniqueness() { // Use reflection to access private method $reflection = new ReflectionClass($this->queue_processor); $method = $reflection->getMethod('generate_job_id'); $method->setAccessible(true); // Generate multiple job IDs $job_ids = []; for ($i = 0; $i < 100; $i++) { $job_id = $method->invoke( $this->queue_processor, EntityMappingService::ENTITY_CUSTOMER, 123, 'create' ); $job_ids[] = $job_id; } // Assert all IDs are unique $unique_ids = array_unique($job_ids); $this->assertEquals(count($job_ids), count($unique_ids)); // Assert ID format foreach ($job_ids as $job_id) { $this->assertStringContains('customer_123_create_', $job_id); } } /** * Test validate queue parameters */ public function test_validate_queue_parameters() { // Use reflection to access private method $reflection = new ReflectionClass($this->queue_processor); $method = $reflection->getMethod('validate_queue_params'); $method->setAccessible(true); // Test valid parameters $result = $method->invoke( $this->queue_processor, EntityMappingService::ENTITY_CUSTOMER, 'create', 'perfex_to_moloni', QueueProcessor::PRIORITY_NORMAL ); $this->assertTrue($result); // Test invalid entity type $result = $method->invoke( $this->queue_processor, 'invalid_entity', 'create', 'perfex_to_moloni', QueueProcessor::PRIORITY_NORMAL ); $this->assertFalse($result); // Test invalid action $result = $method->invoke( $this->queue_processor, EntityMappingService::ENTITY_CUSTOMER, 'invalid_action', 'perfex_to_moloni', QueueProcessor::PRIORITY_NORMAL ); $this->assertFalse($result); // Test invalid direction $result = $method->invoke( $this->queue_processor, EntityMappingService::ENTITY_CUSTOMER, 'create', 'invalid_direction', QueueProcessor::PRIORITY_NORMAL ); $this->assertFalse($result); // Test invalid priority $result = $method->invoke( $this->queue_processor, EntityMappingService::ENTITY_CUSTOMER, 'create', 'perfex_to_moloni', 999 ); $this->assertFalse($result); } protected function tearDown(): void { parent::tearDown(); } }