/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ CI = &get_instance(); $this->CI->load->database(); $this->db = $this->CI->db; if (ENVIRONMENT !== 'testing') { $this->markTestSkipped('Contract tests should only run in testing environment'); } } /** * @test * Contract: desk_moloni_mapping table must exist with correct structure */ public function mapping_table_exists_with_required_structure() { // ACT: Check table existence $table_exists = $this->db->table_exists('desk_moloni_mapping'); // ASSERT: Table must exist $this->assertTrue($table_exists, 'desk_moloni_mapping table must exist'); // ASSERT: Required columns exist $fields = $this->db->list_fields('desk_moloni_mapping'); $required_fields = ['id', 'entity_type', 'perfex_id', 'moloni_id', 'sync_direction', 'last_sync_at', 'created_at', 'updated_at']; foreach ($required_fields as $field) { $this->assertContains($field, $fields, "Required field '{$field}' must exist in desk_moloni_mapping table"); } } /** * @test * Contract: Mapping table must enforce entity_type ENUM values */ public function mapping_table_enforces_entity_type_enum() { // ARRANGE: Clean table $this->db->truncate('desk_moloni_mapping'); // ACT & ASSERT: Valid entity types should work $valid_types = ['client', 'product', 'invoice', 'estimate', 'credit_note']; foreach ($valid_types as $type) { $test_data = [ 'entity_type' => $type, 'perfex_id' => 1, 'moloni_id' => 1, 'sync_direction' => 'bidirectional' ]; $insert_success = $this->db->insert('desk_moloni_mapping', $test_data); $this->assertTrue($insert_success, "Entity type '{$type}' must be valid"); // Clean up for next iteration $this->db->delete('desk_moloni_mapping', ['entity_type' => $type]); } // ACT & ASSERT: Invalid entity type should fail $invalid_data = [ 'entity_type' => 'invalid_type', 'perfex_id' => 1, 'moloni_id' => 1, 'sync_direction' => 'bidirectional' ]; $this->expectException(\Exception::class); $this->db->insert('desk_moloni_mapping', $invalid_data); } /** * @test * Contract: Mapping table must enforce sync_direction ENUM values */ public function mapping_table_enforces_sync_direction_enum() { // ARRANGE: Clean table $this->db->truncate('desk_moloni_mapping'); // ACT & ASSERT: Valid sync directions should work $valid_directions = ['perfex_to_moloni', 'moloni_to_perfex', 'bidirectional']; foreach ($valid_directions as $direction) { $test_data = [ 'entity_type' => 'client', 'perfex_id' => 1, 'moloni_id' => 1, 'sync_direction' => $direction ]; $insert_success = $this->db->insert('desk_moloni_mapping', $test_data); $this->assertTrue($insert_success, "Sync direction '{$direction}' must be valid"); // Clean up for next iteration $this->db->delete('desk_moloni_mapping', ['sync_direction' => $direction]); } // ACT & ASSERT: Invalid sync direction should fail $invalid_data = [ 'entity_type' => 'client', 'perfex_id' => 1, 'moloni_id' => 1, 'sync_direction' => 'invalid_direction' ]; $this->expectException(\Exception::class); $this->db->insert('desk_moloni_mapping', $invalid_data); } /** * @test * Contract: Mapping table must enforce unique constraints */ public function mapping_table_enforces_unique_constraints() { // ARRANGE: Clean table $this->db->truncate('desk_moloni_mapping'); $test_data = [ 'entity_type' => 'client', 'perfex_id' => 123, 'moloni_id' => 456, 'sync_direction' => 'bidirectional' ]; // ACT & ASSERT: First insert should succeed $first_insert = $this->db->insert('desk_moloni_mapping', $test_data); $this->assertTrue($first_insert, 'First insert with unique mapping should succeed'); // ACT & ASSERT: Duplicate perfex_id for same entity_type should fail $duplicate_perfex = [ 'entity_type' => 'client', 'perfex_id' => 123, 'moloni_id' => 789, 'sync_direction' => 'bidirectional' ]; $this->expectException(\Exception::class); $this->db->insert('desk_moloni_mapping', $duplicate_perfex); } /** * @test * Contract: Mapping table must have required indexes for performance */ public function mapping_table_has_required_indexes() { // ACT: Get table indexes $indexes = $this->db->query("SHOW INDEX FROM desk_moloni_mapping")->result_array(); // ASSERT: Required indexes exist $required_indexes = [ 'PRIMARY', 'unique_perfex_mapping', 'unique_moloni_mapping', 'idx_entity_perfex', 'idx_entity_moloni', 'idx_last_sync' ]; $index_names = array_column($indexes, 'Key_name'); foreach ($required_indexes as $required_index) { $this->assertContains($required_index, $index_names, "Required index '{$required_index}' must exist"); } } /** * @test * Contract: Mapping table must support bidirectional relationships */ public function mapping_table_supports_bidirectional_relationships() { // ARRANGE: Clean table $this->db->truncate('desk_moloni_mapping'); // ACT: Insert bidirectional mapping $bidirectional_data = [ 'entity_type' => 'invoice', 'perfex_id' => 100, 'moloni_id' => 200, 'sync_direction' => 'bidirectional', 'last_sync_at' => date('Y-m-d H:i:s') ]; $insert_success = $this->db->insert('desk_moloni_mapping', $bidirectional_data); $this->assertTrue($insert_success, 'Bidirectional mapping must be supported'); // ASSERT: Data retrieved correctly $row = $this->db->get_where('desk_moloni_mapping', ['perfex_id' => 100, 'entity_type' => 'invoice'])->row(); $this->assertEquals('bidirectional', $row->sync_direction, 'Bidirectional sync direction must be stored'); $this->assertEquals(100, $row->perfex_id, 'Perfex ID must be stored correctly'); $this->assertEquals(200, $row->moloni_id, 'Moloni ID must be stored correctly'); $this->assertNotNull($row->last_sync_at, 'last_sync_at must support timestamp values'); } /** * @test * Contract: Mapping table must allow NULL last_sync_at for new mappings */ public function mapping_table_allows_null_last_sync_at() { // ARRANGE: Clean table $this->db->truncate('desk_moloni_mapping'); // ACT: Insert mapping without last_sync_at $new_mapping_data = [ 'entity_type' => 'product', 'perfex_id' => 50, 'moloni_id' => 75, 'sync_direction' => 'perfex_to_moloni' ]; $insert_success = $this->db->insert('desk_moloni_mapping', $new_mapping_data); $this->assertTrue($insert_success, 'New mapping without last_sync_at must be allowed'); // ASSERT: last_sync_at is NULL for new mappings $row = $this->db->get_where('desk_moloni_mapping', ['perfex_id' => 50, 'entity_type' => 'product'])->row(); $this->assertNull($row->last_sync_at, 'last_sync_at must be NULL for new mappings'); } /** * @test * Contract: Mapping table must have automatic created_at and updated_at timestamps */ public function mapping_table_has_automatic_timestamps() { // ARRANGE: Clean table $this->db->truncate('desk_moloni_mapping'); // ACT: Insert mapping $test_data = [ 'entity_type' => 'estimate', 'perfex_id' => 25, 'moloni_id' => 35, 'sync_direction' => 'moloni_to_perfex' ]; $this->db->insert('desk_moloni_mapping', $test_data); // ASSERT: Timestamps are automatically set $row = $this->db->get_where('desk_moloni_mapping', ['perfex_id' => 25, 'entity_type' => 'estimate'])->row(); $this->assertNotNull($row->created_at, 'created_at must be automatically set'); $this->assertNotNull($row->updated_at, 'updated_at must be automatically set'); // ASSERT: Timestamps are recent $created_time = strtotime($row->created_at); $current_time = time(); $this->assertLessThan(5, abs($current_time - $created_time), 'created_at must be recent'); } protected function tearDown(): void { // Clean up test data if ($this->db) { $this->db->where('perfex_id >=', 1); $this->db->where('perfex_id <=', 200); $this->db->delete('desk_moloni_mapping'); } } }