/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ clearTestData(); // Initialize test model (will be implemented after tests) // $this->testLogModel = new DeskMoloniSyncLog(); } 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', 'operation_type', 'entity_type', 'perfex_id', 'moloni_id', 'direction', 'status', 'request_data', 'response_data', 'error_message', 'execution_time_ms', 'created_at' ]; foreach ($expectedColumns as $column) { $this->assertTrue($db->field_exists($column, $this->tableName), "Column '{$column}' should exist in {$this->tableName}"); } } /** * Test operation_type ENUM values */ public function testOperationTypeEnumValues() { $db = $this->ci->db; $validOperationTypes = ['create', 'update', 'delete', 'status_change']; foreach ($validOperationTypes as $operationType) { $data = [ 'operation_type' => $operationType, 'entity_type' => 'client', 'perfex_id' => rand(1, 1000), 'direction' => 'perfex_to_moloni', 'status' => 'success' ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid operation type '{$operationType}' should insert successfully"); $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row(); $this->assertEquals($operationType, $record->operation_type, "Operation type should match inserted value"); // Clean up $db->where('perfex_id', $data['perfex_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 = [ 'operation_type' => 'create', 'entity_type' => $entityType, 'perfex_id' => rand(1, 1000), 'direction' => 'perfex_to_moloni', 'status' => 'success' ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid entity type '{$entityType}' should insert successfully"); // Clean up $db->where('perfex_id', $data['perfex_id'])->delete($this->tableName); } } /** * Test direction ENUM values */ public function testDirectionEnumValues() { $db = $this->ci->db; $validDirections = ['perfex_to_moloni', 'moloni_to_perfex']; foreach ($validDirections as $direction) { $data = [ 'operation_type' => 'update', 'entity_type' => 'product', 'perfex_id' => rand(1, 1000), 'direction' => $direction, 'status' => 'success' ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid direction '{$direction}' should insert successfully"); $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row(); $this->assertEquals($direction, $record->direction, "Direction should match inserted value"); // Clean up $db->where('perfex_id', $data['perfex_id'])->delete($this->tableName); } } /** * Test status ENUM values */ public function testStatusEnumValues() { $db = $this->ci->db; $validStatuses = ['success', 'error', 'warning']; foreach ($validStatuses as $status) { $data = [ 'operation_type' => 'create', 'entity_type' => 'invoice', 'perfex_id' => rand(1, 1000), 'direction' => 'perfex_to_moloni', 'status' => $status ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid status '{$status}' should insert successfully"); $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row(); $this->assertEquals($status, $record->status, "Status should match inserted value"); // Clean up $db->where('perfex_id', $data['perfex_id'])->delete($this->tableName); } } /** * Test entity ID constraints - at least one must be present */ public function testEntityIdConstraints() { $db = $this->ci->db; // Test with perfex_id only $dataWithPerfexId = [ 'operation_type' => 'create', 'entity_type' => 'client', 'perfex_id' => rand(10000, 19999), 'moloni_id' => null, 'direction' => 'perfex_to_moloni', 'status' => 'success' ]; $this->assertTrue($db->insert($this->tableName, $dataWithPerfexId), 'Insert with perfex_id only should succeed'); // Test with moloni_id only $dataWithMoloniId = [ 'operation_type' => 'update', 'entity_type' => 'product', 'perfex_id' => null, 'moloni_id' => rand(10000, 19999), 'direction' => 'moloni_to_perfex', 'status' => 'success' ]; $this->assertTrue($db->insert($this->tableName, $dataWithMoloniId), 'Insert with moloni_id only should succeed'); // Test with both IDs $dataWithBothIds = [ 'operation_type' => 'update', 'entity_type' => 'invoice', 'perfex_id' => rand(20000, 29999), 'moloni_id' => rand(20000, 29999), 'direction' => 'perfex_to_moloni', 'status' => 'success' ]; $this->assertTrue($db->insert($this->tableName, $dataWithBothIds), 'Insert with both IDs should succeed'); } /** * Test JSON validation for request and response data */ public function testJSONValidation() { $db = $this->ci->db; // Test valid JSON data $validJSONData = [ '{"client_id": 123, "name": "Test Client", "email": "test@example.com"}', '{"products": [{"id": 1, "name": "Product 1"}, {"id": 2, "name": "Product 2"}]}', '[]', '{}', null ]; foreach ($validJSONData as $index => $jsonData) { $data = [ 'operation_type' => 'create', 'entity_type' => 'client', 'perfex_id' => rand(30000, 39999) + $index, 'direction' => 'perfex_to_moloni', 'status' => 'success', 'request_data' => $jsonData, 'response_data' => $jsonData ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid JSON data should insert successfully"); $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row(); $this->assertEquals($jsonData, $record->request_data, "Request data should match"); $this->assertEquals($jsonData, $record->response_data, "Response data should match"); } } /** * Test execution time validation */ public function testExecutionTimeValidation() { $db = $this->ci->db; $validExecutionTimes = [0, 50, 150, 1000, 5000]; foreach ($validExecutionTimes as $index => $executionTime) { $data = [ 'operation_type' => 'update', 'entity_type' => 'product', 'perfex_id' => rand(40000, 49999) + $index, 'direction' => 'perfex_to_moloni', 'status' => 'success', 'execution_time_ms' => $executionTime ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid execution time '{$executionTime}ms' should insert successfully"); $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row(); $this->assertEquals($executionTime, $record->execution_time_ms, "Execution time should match"); } } /** * Test successful operation logging */ public function testSuccessfulOperationLogging() { $db = $this->ci->db; $successfulOperation = [ 'operation_type' => 'create', 'entity_type' => 'client', 'perfex_id' => rand(50000, 59999), 'moloni_id' => rand(50000, 59999), 'direction' => 'perfex_to_moloni', 'status' => 'success', 'request_data' => '{"name": "New Client", "email": "client@example.com"}', 'response_data' => '{"id": 12345, "status": "created", "moloni_id": 54321}', 'execution_time_ms' => 250 ]; $this->assertTrue($db->insert($this->tableName, $successfulOperation), 'Successful operation should log correctly'); $logEntry = $db->where('perfex_id', $successfulOperation['perfex_id'])->get($this->tableName)->row(); $this->assertEquals('success', $logEntry->status, 'Status should be success'); $this->assertNull($logEntry->error_message, 'Error message should be NULL for successful operations'); $this->assertNotNull($logEntry->request_data, 'Request data should be logged'); $this->assertNotNull($logEntry->response_data, 'Response data should be logged'); $this->assertEquals(250, $logEntry->execution_time_ms, 'Execution time should be logged'); } /** * Test error operation logging */ public function testErrorOperationLogging() { $db = $this->ci->db; $errorMessage = 'API returned 400 Bad Request: Invalid client data - email field is required'; $errorOperation = [ 'operation_type' => 'create', 'entity_type' => 'client', 'perfex_id' => rand(60000, 69999), 'direction' => 'perfex_to_moloni', 'status' => 'error', 'request_data' => '{"name": "Incomplete Client"}', 'response_data' => '{"error": "email field is required", "code": 400}', 'error_message' => $errorMessage, 'execution_time_ms' => 1200 ]; $this->assertTrue($db->insert($this->tableName, $errorOperation), 'Error operation should log correctly'); $logEntry = $db->where('perfex_id', $errorOperation['perfex_id'])->get($this->tableName)->row(); $this->assertEquals('error', $logEntry->status, 'Status should be error'); $this->assertEquals($errorMessage, $logEntry->error_message, 'Error message should be logged'); $this->assertNotNull($logEntry->request_data, 'Request data should be logged for debugging'); $this->assertNotNull($logEntry->response_data, 'Response data should be logged for debugging'); } /** * Test warning operation logging */ public function testWarningOperationLogging() { $db = $this->ci->db; $warningMessage = 'Operation completed but some fields were ignored due to validation rules'; $warningOperation = [ 'operation_type' => 'update', 'entity_type' => 'product', 'perfex_id' => rand(70000, 79999), 'moloni_id' => rand(70000, 79999), 'direction' => 'perfex_to_moloni', 'status' => 'warning', 'request_data' => '{"name": "Updated Product", "invalid_field": "ignored"}', 'response_data' => '{"id": 12345, "status": "updated", "warnings": ["invalid_field ignored"]}', 'error_message' => $warningMessage, 'execution_time_ms' => 800 ]; $this->assertTrue($db->insert($this->tableName, $warningOperation), 'Warning operation should log correctly'); $logEntry = $db->where('perfex_id', $warningOperation['perfex_id'])->get($this->tableName)->row(); $this->assertEquals('warning', $logEntry->status, 'Status should be warning'); $this->assertEquals($warningMessage, $logEntry->error_message, 'Warning message should be logged'); } /** * 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 log analysis and performance $expectedIndexes = [ 'PRIMARY', 'idx_entity_status', 'idx_perfex_entity', 'idx_moloni_entity', 'idx_created_at', 'idx_operation_direction', 'idx_status', 'idx_execution_time' ]; foreach ($expectedIndexes as $expectedIndex) { $this->assertContains($expectedIndex, $indexNames, "Index '{$expectedIndex}' should exist for log analysis performance"); } } /** * Test log analysis queries */ public function testLogAnalysisQueries() { $db = $this->ci->db; // Insert test log entries for analysis $testLogs = [ ['operation_type' => 'create', 'entity_type' => 'client', 'perfex_id' => 80001, 'status' => 'success', 'execution_time_ms' => 200], ['operation_type' => 'update', 'entity_type' => 'client', 'perfex_id' => 80002, 'status' => 'error', 'execution_time_ms' => 1500], ['operation_type' => 'create', 'entity_type' => 'product', 'perfex_id' => 80003, 'status' => 'success', 'execution_time_ms' => 300], ['operation_type' => 'delete', 'entity_type' => 'invoice', 'perfex_id' => 80004, 'status' => 'success', 'execution_time_ms' => 100] ]; foreach ($testLogs as $log) { $log['direction'] = 'perfex_to_moloni'; $db->insert($this->tableName, $log); } // Test error analysis query $errorCount = $db->where('status', 'error') ->where('created_at >=', date('Y-m-d', strtotime('-1 day'))) ->count_all_results($this->tableName); $this->assertGreaterThanOrEqual(1, $errorCount, 'Should find error logs'); // Test performance analysis query $slowOperations = $db->where('execution_time_ms >', 1000) ->order_by('execution_time_ms', 'DESC') ->get($this->tableName) ->result_array(); $this->assertGreaterThanOrEqual(1, count($slowOperations), 'Should find slow operations'); // Test entity-specific analysis $clientOperations = $db->where('entity_type', 'client') ->where('created_at >=', date('Y-m-d')) ->get($this->tableName) ->result_array(); $this->assertGreaterThanOrEqual(2, count($clientOperations), 'Should find client operations'); } /** * Test timestamp auto-population */ public function testTimestampAutoPopulation() { $db = $this->ci->db; $beforeInsert = time(); $data = [ 'operation_type' => 'status_change', 'entity_type' => 'invoice', 'perfex_id' => rand(90000, 99999), 'direction' => 'moloni_to_perfex', 'status' => 'success' ]; $this->assertTrue($db->insert($this->tableName, $data), 'Insert should succeed'); $afterInsert = time(); $record = $db->where('perfex_id', $data['perfex_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'); } /** * Test audit trail completeness */ public function testAuditTrailCompleteness() { $db = $this->ci->db; // Simulate complete operation audit trail $operationId = rand(100000, 199999); $auditTrail = [ [ 'operation_type' => 'create', 'entity_type' => 'client', 'perfex_id' => $operationId, 'direction' => 'perfex_to_moloni', 'status' => 'success', 'request_data' => '{"name": "Audit Test Client", "email": "audit@test.com"}', 'response_data' => '{"id": ' . $operationId . ', "moloni_id": ' . ($operationId + 1000) . '}', 'execution_time_ms' => 300 ], [ 'operation_type' => 'update', 'entity_type' => 'client', 'perfex_id' => $operationId, 'moloni_id' => $operationId + 1000, 'direction' => 'perfex_to_moloni', 'status' => 'success', 'request_data' => '{"name": "Updated Audit Test Client"}', 'response_data' => '{"id": ' . ($operationId + 1000) . ', "status": "updated"}', 'execution_time_ms' => 200 ] ]; foreach ($auditTrail as $entry) { $this->assertTrue($db->insert($this->tableName, $entry), 'Audit entry should insert'); } // Verify complete audit trail exists $auditEntries = $db->where('perfex_id', $operationId) ->order_by('created_at', 'ASC') ->get($this->tableName) ->result_array(); $this->assertEquals(2, count($auditEntries), 'Should have complete audit trail'); $this->assertEquals('create', $auditEntries[0]['operation_type'], 'First entry should be create'); $this->assertEquals('update', $auditEntries[1]['operation_type'], 'Second entry should be update'); } /** * Helper method to clear test data */ private function clearTestData() { $db = $this->ci->db; // Clear test data using wide ID ranges $idRanges = [ ['min' => 1, 'max' => 199999] // Covers all test ranges ]; foreach ($idRanges as $range) { $db->where('perfex_id >=', $range['min']) ->where('perfex_id <=', $range['max']) ->delete($this->tableName); $db->where('moloni_id >=', $range['min']) ->where('moloni_id <=', $range['max']) ->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 audit integrity'); } }