- Added GitHub spec-kit for development workflow - Standardized file signatures to Descomplicar® format - Updated development configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
477 lines
16 KiB
PHP
477 lines
16 KiB
PHP
/**
|
|
* Descomplicar® Crescimento Digital
|
|
* https://descomplicar.pt
|
|
*/
|
|
|
|
<?php
|
|
|
|
/**
|
|
* MappingTableTest.php
|
|
*
|
|
* PHPUnit tests for desk_moloni_mapping table structure and validation rules
|
|
* Tests bidirectional entity mapping between Perfex and Moloni
|
|
*
|
|
* @package DeskMoloni\Tests\Database
|
|
* @author Database Design Specialist
|
|
* @version 3.0
|
|
*/
|
|
|
|
require_once(__DIR__ . '/../../../../tests/TestCase.php');
|
|
|
|
class MappingTableTest extends TestCase
|
|
{
|
|
private $tableName = 'desk_moloni_mapping';
|
|
private $testMappingModel;
|
|
|
|
public function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->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');
|
|
}
|
|
} |