Files
desk-moloni/modules/desk_moloni/tests/Unit/QueueProcessorTest.php
Emanuel Almeida c19f6fd9ee fix(perfexcrm module): align version to 3.0.1, unify entrypoint, and harden routes/views
- Bump DESK_MOLONI version to 3.0.1 across module
- Normalize hooks to after_client_* and instantiate PerfexHooks safely
- Fix OAuthController view path and API client class name
- Add missing admin views for webhook config/logs; adjust view loading
- Harden client portal routes and admin routes mapping
- Make Dashboard/Logs/Queue tolerant to optional model methods
- Align log details query with existing schema; avoid broken joins

This makes the module operational in Perfex (admin + client), reduces 404s,
and avoids fatal errors due to inconsistent tables/methods.
2025-09-11 17:38:45 +01:00

529 lines
15 KiB
PHP

<?php
defined('BASEPATH') or exit('No direct script access allowed');
/**
* Queue Processor Unit Tests
* Comprehensive test suite for QueueProcessor functionality
*
* @package DeskMoloni
* @subpackage Tests\Unit
* @category UnitTests
* @author Descomplicar® - PHP Fullstack Engineer
* @version 1.0.0
*/
use PHPUnit\Framework\TestCase;
use DeskMoloni\Libraries\QueueProcessor;
use DeskMoloni\Libraries\EntityMappingService;
class QueueProcessorTest extends TestCase
{
protected $queue_processor;
protected $redis_mock;
protected $model_mock;
protected function setUp(): void
{
parent::setUp();
// Mock Redis connection
$this->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();
}
}