/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ clearTestData(); // Initialize test model (will be implemented after tests) // $this->testMappingModel = new DeskMoloniMapping(); } 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', 'entity_type', 'perfex_id', 'moloni_id', 'sync_direction', 'last_sync_at', 'created_at', 'updated_at' ]; foreach ($expectedColumns as $column) { $this->assertTrue($db->field_exists($column, $this->tableName), "Column '{$column}' should exist in {$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 = [ 'entity_type' => $entityType, 'perfex_id' => rand(1, 1000), 'moloni_id' => rand(1, 1000), 'sync_direction' => 'bidirectional' ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid entity type '{$entityType}' should insert successfully"); // Clean up immediately to avoid constraint conflicts $db->where('entity_type', $entityType) ->where('perfex_id', $data['perfex_id']) ->delete($this->tableName); } } /** * Test sync_direction ENUM values */ public function testSyncDirectionEnumValues() { $db = $this->ci->db; $validDirections = ['perfex_to_moloni', 'moloni_to_perfex', 'bidirectional']; foreach ($validDirections as $direction) { $data = [ 'entity_type' => 'client', 'perfex_id' => rand(1, 1000), 'moloni_id' => rand(1, 1000), 'sync_direction' => $direction ]; $this->assertTrue($db->insert($this->tableName, $data), "Valid sync direction '{$direction}' should insert successfully"); // Verify stored value $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row(); $this->assertEquals($direction, $record->sync_direction, "Sync direction should match inserted value"); // Clean up $db->where('perfex_id', $data['perfex_id'])->delete($this->tableName); } } /** * Test default sync_direction value */ public function testDefaultSyncDirection() { $db = $this->ci->db; $data = [ 'entity_type' => 'product', 'perfex_id' => rand(1, 1000), 'moloni_id' => rand(1, 1000) // sync_direction omitted to test default ]; $this->assertTrue($db->insert($this->tableName, $data), 'Insert without sync_direction should succeed'); $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row(); $this->assertEquals('bidirectional', $record->sync_direction, 'Default sync_direction should be bidirectional'); } /** * Test unique constraint on entity_type + perfex_id */ public function testUniquePerfexMapping() { $db = $this->ci->db; // Insert first record $data1 = [ 'entity_type' => 'invoice', 'perfex_id' => 12345, 'moloni_id' => 54321, 'sync_direction' => 'bidirectional' ]; $this->assertTrue($db->insert($this->tableName, $data1), 'First mapping insert should succeed'); // Try to insert duplicate perfex mapping - should fail $data2 = [ 'entity_type' => 'invoice', // Same entity type 'perfex_id' => 12345, // Same perfex ID 'moloni_id' => 98765, // Different moloni ID 'sync_direction' => 'perfex_to_moloni' ]; $this->assertFalse($db->insert($this->tableName, $data2), 'Duplicate perfex mapping should fail'); $this->assertStringContainsString('Duplicate', $db->error()['message']); } /** * Test unique constraint on entity_type + moloni_id */ public function testUniqueMoloniMapping() { $db = $this->ci->db; // Insert first record $data1 = [ 'entity_type' => 'client', 'perfex_id' => 11111, 'moloni_id' => 22222, 'sync_direction' => 'bidirectional' ]; $this->assertTrue($db->insert($this->tableName, $data1), 'First mapping insert should succeed'); // Try to insert duplicate moloni mapping - should fail $data2 = [ 'entity_type' => 'client', // Same entity type 'perfex_id' => 33333, // Different perfex ID 'moloni_id' => 22222, // Same moloni ID 'sync_direction' => 'moloni_to_perfex' ]; $this->assertFalse($db->insert($this->tableName, $data2), 'Duplicate moloni mapping should fail'); $this->assertStringContainsString('Duplicate', $db->error()['message']); } /** * Test that same IDs can exist for different entity types */ public function testDifferentEntityTypesAllowSameIds() { $db = $this->ci->db; $sameId = 99999; // Insert mappings with same IDs but different entity types $mappings = [ ['entity_type' => 'client', 'perfex_id' => $sameId, 'moloni_id' => $sameId], ['entity_type' => 'product', 'perfex_id' => $sameId, 'moloni_id' => $sameId], ['entity_type' => 'invoice', 'perfex_id' => $sameId, 'moloni_id' => $sameId] ]; foreach ($mappings as $mapping) { $mapping['sync_direction'] = 'bidirectional'; $this->assertTrue($db->insert($this->tableName, $mapping), "Same IDs should be allowed for different entity types: {$mapping['entity_type']}"); } // Verify all records exist $count = $db->where('perfex_id', $sameId)->count_all_results($this->tableName); $this->assertEquals(3, $count, 'Should have 3 mappings with same IDs but different entity types'); } /** * Test last_sync_at timestamp handling */ public function testLastSyncAtTimestamp() { $db = $this->ci->db; // Insert record without last_sync_at $data = [ 'entity_type' => 'estimate', 'perfex_id' => rand(1, 1000), 'moloni_id' => rand(1, 1000), 'sync_direction' => 'bidirectional' ]; $this->assertTrue($db->insert($this->tableName, $data), 'Insert should succeed'); $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row(); $this->assertNull($record->last_sync_at, 'last_sync_at should be NULL initially'); // Update with sync timestamp $syncTime = date('Y-m-d H:i:s'); $db->where('perfex_id', $data['perfex_id'])->update($this->tableName, ['last_sync_at' => $syncTime]); $updatedRecord = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row(); $this->assertEquals($syncTime, $updatedRecord->last_sync_at, 'last_sync_at should be updated'); } /** * 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 $expectedIndexes = [ 'PRIMARY', 'unique_perfex_mapping', 'unique_moloni_mapping', 'idx_entity_perfex', 'idx_entity_moloni', 'idx_sync_direction', 'idx_last_sync', 'idx_created_at' ]; foreach ($expectedIndexes as $expectedIndex) { $this->assertContains($expectedIndex, $indexNames, "Index '{$expectedIndex}' should exist for performance optimization"); } } /** * Test bidirectional mapping functionality */ public function testBidirectionalMappingScenarios() { $db = $this->ci->db; // Test Perfex to Moloni sync $perfexToMoloni = [ 'entity_type' => 'client', 'perfex_id' => 100, 'moloni_id' => 200, 'sync_direction' => 'perfex_to_moloni' ]; $this->assertTrue($db->insert($this->tableName, $perfexToMoloni), 'Perfex to Moloni mapping should insert successfully'); // Test Moloni to Perfex sync $moloniToPerfex = [ 'entity_type' => 'product', 'perfex_id' => 300, 'moloni_id' => 400, 'sync_direction' => 'moloni_to_perfex' ]; $this->assertTrue($db->insert($this->tableName, $moloniToPerfex), 'Moloni to Perfex mapping should insert successfully'); // Test bidirectional sync $bidirectional = [ 'entity_type' => 'invoice', 'perfex_id' => 500, 'moloni_id' => 600, 'sync_direction' => 'bidirectional' ]; $this->assertTrue($db->insert($this->tableName, $bidirectional), 'Bidirectional mapping should insert successfully'); // Verify mappings can be retrieved by direction $perfexDirection = $db->where('sync_direction', 'perfex_to_moloni')->count_all_results($this->tableName); $this->assertGreaterThanOrEqual(1, $perfexDirection, 'Should find perfex_to_moloni mappings'); $moloniDirection = $db->where('sync_direction', 'moloni_to_perfex')->count_all_results($this->tableName); $this->assertGreaterThanOrEqual(1, $moloniDirection, 'Should find moloni_to_perfex mappings'); $bidirectionalCount = $db->where('sync_direction', 'bidirectional')->count_all_results($this->tableName); $this->assertGreaterThanOrEqual(1, $bidirectionalCount, 'Should find bidirectional mappings'); } /** * Test entity lookup by Perfex ID */ public function testPerfexEntityLookup() { $db = $this->ci->db; $testMappings = [ ['entity_type' => 'client', 'perfex_id' => 1001, 'moloni_id' => 2001], ['entity_type' => 'product', 'perfex_id' => 1002, 'moloni_id' => 2002], ['entity_type' => 'invoice', 'perfex_id' => 1003, 'moloni_id' => 2003] ]; foreach ($testMappings as $mapping) { $mapping['sync_direction'] = 'bidirectional'; $db->insert($this->tableName, $mapping); } // Test lookup by entity type and perfex ID foreach ($testMappings as $expected) { $result = $db->where('entity_type', $expected['entity_type']) ->where('perfex_id', $expected['perfex_id']) ->get($this->tableName) ->row(); $this->assertNotNull($result, "Should find mapping for {$expected['entity_type']} with perfex_id {$expected['perfex_id']}"); $this->assertEquals($expected['moloni_id'], $result->moloni_id, 'Moloni ID should match'); } } /** * Test entity lookup by Moloni ID */ public function testMoloniEntityLookup() { $db = $this->ci->db; $testMappings = [ ['entity_type' => 'estimate', 'perfex_id' => 3001, 'moloni_id' => 4001], ['entity_type' => 'credit_note', 'perfex_id' => 3002, 'moloni_id' => 4002] ]; foreach ($testMappings as $mapping) { $mapping['sync_direction'] = 'bidirectional'; $db->insert($this->tableName, $mapping); } // Test lookup by entity type and moloni ID foreach ($testMappings as $expected) { $result = $db->where('entity_type', $expected['entity_type']) ->where('moloni_id', $expected['moloni_id']) ->get($this->tableName) ->row(); $this->assertNotNull($result, "Should find mapping for {$expected['entity_type']} with moloni_id {$expected['moloni_id']}"); $this->assertEquals($expected['perfex_id'], $result->perfex_id, 'Perfex ID should match'); } } /** * Test timestamp fields auto-population */ public function testTimestampFields() { $db = $this->ci->db; $beforeInsert = time(); $data = [ 'entity_type' => 'client', 'perfex_id' => rand(5000, 9999), 'moloni_id' => rand(5000, 9999), 'sync_direction' => 'bidirectional' ]; $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'); // Verify updated_at is populated $this->assertNotNull($record->updated_at, 'updated_at should be auto-populated'); $this->assertEquals($record->created_at, $record->updated_at, 'Initially created_at should equal updated_at'); } /** * Helper method to clear test data */ private function clearTestData() { $db = $this->ci->db; // Clear all test data - using wide range to catch test IDs $db->where('perfex_id >=', 1) ->where('perfex_id <=', 9999) ->delete($this->tableName); $db->where('moloni_id >=', 1) ->where('moloni_id <=', 9999) ->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 foreign key support'); } }