/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ clearTestData(); // Initialize test model (will be implemented after tests) // $this->testQueueModel = new DeskMoloniSyncQueue(); } public function tearDown(): void { $this->clearTestData(); parent::tearDown(); } /** * Test table structure exists with correct columns */ public function testTableStructureExists() { $db = $this->ci->db; // Verify table exists $this->assertTrue($db->table_exists($this->tableName), "Table {$this->tableName} should exist"); // Verify required columns exist $expectedColumns = [ 'id', 'task_type', 'entity_type', 'entity_id', 'priority', 'payload', 'status', 'attempts', 'max_attempts', 'scheduled_at', 'started_at', 'completed_at', 'error_message', 'created_at', 'updated_at' ]; foreach ($expectedColumns as $column) { $this->assertTrue($db->field_exists($column, $this->tableName), "Column '{$column}' should exist in {$this->tableName}"); } } /** * Test task_type ENUM values */ public function testTaskTypeEnumValues() { $db = $this->ci->db; $validTaskTypes = [ 'sync_client', 'sync_product', 'sync_invoice', 'sync_estimate', 'sync_credit_note', 'status_update' ]; foreach ($validTaskTypes as $taskType) { $data = [ 'task_type' => $taskType, 'entity_type' => 'client', 'entity_id' => rand(1, 1000), 'priority' => 5 ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid task type '{$taskType}' should insert successfully"); $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row(); $this->assertEquals($taskType, $record->task_type, "Task type should match inserted value"); // Clean up $db->where('entity_id', $data['entity_id'])->delete($this->tableName); } } /** * Test entity_type ENUM values */ public function testEntityTypeEnumValues() { $db = $this->ci->db; $validEntityTypes = ['client', 'product', 'invoice', 'estimate', 'credit_note']; foreach ($validEntityTypes as $entityType) { $data = [ 'task_type' => 'sync_client', 'entity_type' => $entityType, 'entity_id' => rand(1, 1000), 'priority' => 5 ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid entity type '{$entityType}' should insert successfully"); // Clean up $db->where('entity_id', $data['entity_id'])->delete($this->tableName); } } /** * Test status ENUM values and state transitions */ public function testStatusEnumValues() { $db = $this->ci->db; $validStatuses = ['pending', 'processing', 'completed', 'failed', 'retry']; foreach ($validStatuses as $status) { $data = [ 'task_type' => 'sync_product', 'entity_type' => 'product', 'entity_id' => rand(1, 1000), 'priority' => 5, 'status' => $status ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid status '{$status}' should insert successfully"); $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row(); $this->assertEquals($status, $record->status, "Status should match inserted value"); // Clean up $db->where('entity_id', $data['entity_id'])->delete($this->tableName); } } /** * Test default values */ public function testDefaultValues() { $db = $this->ci->db; $data = [ 'task_type' => 'sync_invoice', 'entity_type' => 'invoice', 'entity_id' => rand(1, 1000) // Omit priority, status, attempts, max_attempts to test defaults ]; $this->assertTrue($db->insert($this->tableName, $data), 'Insert with default values should succeed'); $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row(); $this->assertEquals(5, $record->priority, 'Default priority should be 5'); $this->assertEquals('pending', $record->status, 'Default status should be pending'); $this->assertEquals(0, $record->attempts, 'Default attempts should be 0'); $this->assertEquals(3, $record->max_attempts, 'Default max_attempts should be 3'); $this->assertNotNull($record->scheduled_at, 'scheduled_at should be auto-populated'); } /** * Test priority validation constraints */ public function testPriorityConstraints() { $db = $this->ci->db; // Test valid priority range (1-9) $validPriorities = [1, 2, 3, 4, 5, 6, 7, 8, 9]; foreach ($validPriorities as $priority) { $data = [ 'task_type' => 'sync_client', 'entity_type' => 'client', 'entity_id' => rand(1, 1000), 'priority' => $priority ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid priority '{$priority}' should insert successfully"); // Clean up $db->where('entity_id', $data['entity_id'])->delete($this->tableName); } // Test invalid priority values should fail (if constraints are enforced) $invalidPriorities = [0, 10, -1, 15]; foreach ($invalidPriorities as $priority) { $data = [ 'task_type' => 'sync_client', 'entity_type' => 'client', 'entity_id' => rand(1, 1000), 'priority' => $priority ]; // Note: This test depends on database constraint enforcement // Some databases may not enforce CHECK constraints $result = $db->insert($this->tableName, $data); if ($result === false) { $this->assertStringContainsString('constraint', strtolower($db->error()['message'])); } // Clean up any successful inserts $db->where('entity_id', $data['entity_id'])->delete($this->tableName); } } /** * Test attempts validation constraints */ public function testAttemptsConstraints() { $db = $this->ci->db; // Test valid attempts configuration $data = [ 'task_type' => 'sync_product', 'entity_type' => 'product', 'entity_id' => rand(1, 1000), 'priority' => 5, 'attempts' => 2, 'max_attempts' => 3 ]; $this->assertTrue($db->insert($this->tableName, $data), 'Valid attempts configuration should succeed'); $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row(); $this->assertEquals(2, $record->attempts, 'Attempts should match inserted value'); $this->assertEquals(3, $record->max_attempts, 'Max attempts should match inserted value'); } /** * Test JSON payload validation */ public function testJSONPayloadValidation() { $db = $this->ci->db; // Test valid JSON payload $validPayloads = [ '{"action": "create", "data": {"name": "Test Client"}}', '{"sync_fields": ["name", "email", "phone"]}', '[]', '{}', null // NULL should be allowed ]; foreach ($validPayloads as $index => $payload) { $data = [ 'task_type' => 'sync_client', 'entity_type' => 'client', 'entity_id' => rand(10000, 19999) + $index, 'priority' => 5, 'payload' => $payload ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid JSON payload should insert successfully"); $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row(); $this->assertEquals($payload, $record->payload, "Payload should match inserted value"); } } /** * Test task state transitions */ public function testTaskStateTransitions() { $db = $this->ci->db; $entityId = rand(20000, 29999); // Insert pending task $data = [ 'task_type' => 'sync_invoice', 'entity_type' => 'invoice', 'entity_id' => $entityId, 'priority' => 3, 'status' => 'pending' ]; $this->assertTrue($db->insert($this->tableName, $data), 'Pending task should insert'); // Transition to processing $startTime = date('Y-m-d H:i:s'); $updateData = [ 'status' => 'processing', 'started_at' => $startTime, 'attempts' => 1 ]; $db->where('entity_id', $entityId)->update($this->tableName, $updateData); $record = $db->where('entity_id', $entityId)->get($this->tableName)->row(); $this->assertEquals('processing', $record->status, 'Status should be updated to processing'); $this->assertEquals($startTime, $record->started_at, 'started_at should be updated'); $this->assertEquals(1, $record->attempts, 'Attempts should be incremented'); // Transition to completed $completedTime = date('Y-m-d H:i:s'); $completedData = [ 'status' => 'completed', 'completed_at' => $completedTime ]; $db->where('entity_id', $entityId)->update($this->tableName, $completedData); $finalRecord = $db->where('entity_id', $entityId)->get($this->tableName)->row(); $this->assertEquals('completed', $finalRecord->status, 'Status should be updated to completed'); $this->assertEquals($completedTime, $finalRecord->completed_at, 'completed_at should be updated'); } /** * Test failed task with error message */ public function testFailedTaskHandling() { $db = $this->ci->db; $entityId = rand(30000, 39999); $errorMessage = 'API connection timeout after 30 seconds'; $data = [ 'task_type' => 'sync_client', 'entity_type' => 'client', 'entity_id' => $entityId, 'priority' => 5, 'status' => 'failed', 'attempts' => 3, 'max_attempts' => 3, 'error_message' => $errorMessage, 'completed_at' => date('Y-m-d H:i:s') ]; $this->assertTrue($db->insert($this->tableName, $data), 'Failed task should insert'); $record = $db->where('entity_id', $entityId)->get($this->tableName)->row(); $this->assertEquals('failed', $record->status, 'Status should be failed'); $this->assertEquals($errorMessage, $record->error_message, 'Error message should be stored'); $this->assertEquals(3, $record->attempts, 'Should have maximum attempts'); } /** * Test retry logic */ public function testRetryLogic() { $db = $this->ci->db; $entityId = rand(40000, 49999); // Insert failed task that can be retried $data = [ 'task_type' => 'sync_product', 'entity_type' => 'product', 'entity_id' => $entityId, 'priority' => 5, 'status' => 'retry', 'attempts' => 1, 'max_attempts' => 3, 'error_message' => 'Temporary API error', 'scheduled_at' => date('Y-m-d H:i:s', strtotime('+5 minutes')) ]; $this->assertTrue($db->insert($this->tableName, $data), 'Retry task should insert'); $record = $db->where('entity_id', $entityId)->get($this->tableName)->row(); $this->assertEquals('retry', $record->status, 'Status should be retry'); $this->assertEquals(1, $record->attempts, 'Should have 1 attempt'); $this->assertLessThan(3, $record->attempts, 'Should be below max attempts'); } /** * Test performance indexes exist */ public function testPerformanceIndexes() { $db = $this->ci->db; $query = "SHOW INDEX FROM {$this->tableName}"; $indexes = $db->query($query)->result_array(); $indexNames = array_column($indexes, 'Key_name'); // Expected indexes for queue processing performance $expectedIndexes = [ 'PRIMARY', 'idx_status_priority', 'idx_entity', 'idx_scheduled', 'idx_task_status', 'idx_attempts', 'idx_created_at' ]; foreach ($expectedIndexes as $expectedIndex) { $this->assertContains($expectedIndex, $indexNames, "Index '{$expectedIndex}' should exist for queue processing performance"); } } /** * Test queue processing query performance */ public function testQueueProcessingQueries() { $db = $this->ci->db; // Insert test queue items $testTasks = [ ['task_type' => 'sync_client', 'entity_type' => 'client', 'entity_id' => 50001, 'priority' => 1, 'status' => 'pending'], ['task_type' => 'sync_product', 'entity_type' => 'product', 'entity_id' => 50002, 'priority' => 3, 'status' => 'pending'], ['task_type' => 'sync_invoice', 'entity_type' => 'invoice', 'entity_id' => 50003, 'priority' => 2, 'status' => 'processing'], ['task_type' => 'status_update', 'entity_type' => 'invoice', 'entity_id' => 50004, 'priority' => 5, 'status' => 'completed'] ]; foreach ($testTasks as $task) { $db->insert($this->tableName, $task); } // Test typical queue processing query $pendingTasks = $db->where('status', 'pending') ->where('scheduled_at <=', date('Y-m-d H:i:s')) ->order_by('priority', 'ASC') ->order_by('scheduled_at', 'ASC') ->limit(10) ->get($this->tableName) ->result_array(); $this->assertGreaterThan(0, count($pendingTasks), 'Should find pending tasks'); // Verify priority ordering if (count($pendingTasks) > 1) { $this->assertLessThanOrEqual($pendingTasks[1]['priority'], $pendingTasks[0]['priority'], 'Tasks should be ordered by priority (ascending)'); } // Test entity-specific queries $clientTasks = $db->where('entity_type', 'client') ->where('entity_id', 50001) ->get($this->tableName) ->result_array(); $this->assertEquals(1, count($clientTasks), 'Should find entity-specific tasks'); } /** * Test timestamp fields auto-population */ public function testTimestampFields() { $db = $this->ci->db; $beforeInsert = time(); $data = [ 'task_type' => 'sync_estimate', 'entity_type' => 'estimate', 'entity_id' => rand(60000, 69999), 'priority' => 5 ]; $this->assertTrue($db->insert($this->tableName, $data), 'Insert should succeed'); $afterInsert = time(); $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row(); // Verify created_at is populated $this->assertNotNull($record->created_at, 'created_at should be auto-populated'); $createdTimestamp = strtotime($record->created_at); $this->assertGreaterThanOrEqual($beforeInsert, $createdTimestamp, 'created_at should be recent'); $this->assertLessThanOrEqual($afterInsert, $createdTimestamp, 'created_at should not be in future'); // Verify scheduled_at is populated $this->assertNotNull($record->scheduled_at, 'scheduled_at should be auto-populated'); // Verify optional timestamps are NULL $this->assertNull($record->started_at, 'started_at should be NULL initially'); $this->assertNull($record->completed_at, 'completed_at should be NULL initially'); } /** * Helper method to clear test data */ private function clearTestData() { $db = $this->ci->db; // Clear test data using wide entity_id ranges $db->where('entity_id >=', 1) ->where('entity_id <=', 69999) ->delete($this->tableName); } /** * Test character set and collation */ public function testCharacterSetAndCollation() { $db = $this->ci->db; $query = "SELECT TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '{$this->tableName}'"; $result = $db->query($query)->row(); $this->assertEquals('utf8mb4_unicode_ci', $result->TABLE_COLLATION, 'Table should use utf8mb4_unicode_ci collation for proper Unicode support'); } /** * Test storage engine */ public function testStorageEngine() { $db = $this->ci->db; $query = "SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '{$this->tableName}'"; $result = $db->query($query)->row(); $this->assertEquals('InnoDB', $result->ENGINE, 'Table should use InnoDB engine for ACID compliance and transaction support'); } }