Files
desk-moloni/modules/desk_moloni/tests/Integration/ClientSyncIntegrationTest.php
Emanuel Almeida 8c4f68576f chore: add spec-kit and standardize signatures
- 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>
2025-09-12 01:27:37 +01:00

443 lines
15 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
defined('BASEPATH') or exit('No direct script access allowed');
/**
* Client Sync Integration Tests
* End-to-end integration tests for client synchronization between Perfex CRM and Moloni ERP
*
* @package DeskMoloni
* @subpackage Tests\Integration
* @category IntegrationTests
* @author Descomplicar® - PHP Fullstack Engineer
* @version 1.0.0
*/
use PHPUnit\Framework\TestCase;
use DeskMoloni\Libraries\ClientSyncService;
use DeskMoloni\Libraries\EntityMappingService;
use DeskMoloni\Libraries\MoloniApiClient;
class ClientSyncIntegrationTest extends TestCase
{
protected $client_sync;
protected $entity_mapping;
protected $api_client_mock;
protected $test_client_data;
protected $test_moloni_data;
protected function setUp(): void
{
parent::setUp();
// Initialize services
$this->client_sync = new ClientSyncService();
$this->entity_mapping = new EntityMappingService();
// Mock API client
$this->api_client_mock = $this->createMock(MoloniApiClient::class);
// Set up test data
$this->setupTestData();
// Inject mocked API client
$reflection = new ReflectionClass($this->client_sync);
$property = $reflection->getProperty('api_client');
$property->setAccessible(true);
$property->setValue($this->client_sync, $this->api_client_mock);
}
protected function setupTestData()
{
$this->test_client_data = [
'userid' => 999,
'company' => 'Test Company Ltd',
'vat' => 'PT123456789',
'email' => 'test@testcompany.com',
'phonenumber' => '+351234567890',
'website' => 'https://testcompany.com',
'billing_street' => 'Test Street, 123',
'billing_city' => 'Lisbon',
'billing_state' => 'Lisboa',
'billing_zip' => '1000-001',
'billing_country' => 'PT',
'admin_notes' => 'Test client for integration testing'
];
$this->test_moloni_data = [
'customer_id' => 888,
'name' => 'Test Company Ltd',
'vat' => 'PT123456789',
'email' => 'test@testcompany.com',
'phone' => '+351234567890',
'website' => 'https://testcompany.com',
'address' => 'Test Street, 123',
'city' => 'Lisbon',
'state' => 'Lisboa',
'zip_code' => '1000-001',
'country_id' => 1,
'notes' => 'Test client for integration testing'
];
}
/**
* Test complete Perfex to Moloni sync workflow
*/
public function test_complete_perfex_to_moloni_sync_workflow()
{
// Arrange
$perfex_client_id = $this->test_client_data['userid'];
// Mock Perfex client retrieval
$this->mockPerfexClientRetrieval($perfex_client_id, $this->test_client_data);
// Mock successful Moloni API creation
$this->api_client_mock->expects($this->once())
->method('create_customer')
->willReturn([
'success' => true,
'data' => [
'customer_id' => $this->test_moloni_data['customer_id']
]
]);
// Act
$result = $this->client_sync->sync_perfex_to_moloni($perfex_client_id);
// Assert
$this->assertTrue($result['success']);
$this->assertEquals('create', $result['action']);
$this->assertEquals($this->test_moloni_data['customer_id'], $result['moloni_customer_id']);
$this->assertIsInt($result['mapping_id']);
$this->assertGreaterThan(0, $result['execution_time']);
// Verify mapping was created
$mapping = $this->entity_mapping->get_mapping_by_perfex_id(
EntityMappingService::ENTITY_CUSTOMER,
$perfex_client_id
);
$this->assertNotNull($mapping);
$this->assertEquals($perfex_client_id, $mapping->perfex_id);
$this->assertEquals($this->test_moloni_data['customer_id'], $mapping->moloni_id);
$this->assertEquals(EntityMappingService::STATUS_SYNCED, $mapping->sync_status);
}
/**
* Test complete Moloni to Perfex sync workflow
*/
public function test_complete_moloni_to_perfex_sync_workflow()
{
// Arrange
$moloni_customer_id = $this->test_moloni_data['customer_id'];
// Mock Moloni API response
$this->api_client_mock->expects($this->once())
->method('get_customer')
->with($moloni_customer_id)
->willReturn([
'success' => true,
'data' => $this->test_moloni_data
]);
// Mock Perfex client creation
$this->mockPerfexClientCreation($this->test_client_data['userid']);
// Act
$result = $this->client_sync->sync_moloni_to_perfex($moloni_customer_id);
// Assert
$this->assertTrue($result['success']);
$this->assertEquals('create', $result['action']);
$this->assertEquals($this->test_client_data['userid'], $result['perfex_client_id']);
$this->assertIsInt($result['mapping_id']);
// Verify mapping was created
$mapping = $this->entity_mapping->get_mapping_by_moloni_id(
EntityMappingService::ENTITY_CUSTOMER,
$moloni_customer_id
);
$this->assertNotNull($mapping);
$this->assertEquals($this->test_client_data['userid'], $mapping->perfex_id);
$this->assertEquals($moloni_customer_id, $mapping->moloni_id);
$this->assertEquals(EntityMappingService::STATUS_SYNCED, $mapping->sync_status);
}
/**
* Test sync with existing mapping (update scenario)
*/
public function test_sync_with_existing_mapping_update()
{
// Arrange - Create existing mapping
$perfex_client_id = $this->test_client_data['userid'];
$moloni_customer_id = $this->test_moloni_data['customer_id'];
$mapping_id = $this->entity_mapping->create_mapping(
EntityMappingService::ENTITY_CUSTOMER,
$perfex_client_id,
$moloni_customer_id,
EntityMappingService::DIRECTION_PERFEX_TO_MOLONI
);
// Mock Perfex client retrieval
$this->mockPerfexClientRetrieval($perfex_client_id, $this->test_client_data);
// Mock successful Moloni API update
$this->api_client_mock->expects($this->once())
->method('update_customer')
->with($moloni_customer_id)
->willReturn([
'success' => true,
'data' => $this->test_moloni_data
]);
// Act
$result = $this->client_sync->sync_perfex_to_moloni($perfex_client_id, true); // Force update
// Assert
$this->assertTrue($result['success']);
$this->assertEquals('update', $result['action']);
$this->assertEquals($mapping_id, $result['mapping_id']);
}
/**
* Test conflict detection and handling
*/
public function test_conflict_detection_and_handling()
{
// Arrange - Create mapping with conflicting data
$perfex_client_id = $this->test_client_data['userid'];
$moloni_customer_id = $this->test_moloni_data['customer_id'];
$mapping_id = $this->entity_mapping->create_mapping(
EntityMappingService::ENTITY_CUSTOMER,
$perfex_client_id,
$moloni_customer_id,
EntityMappingService::DIRECTION_BIDIRECTIONAL
);
// Set last sync time in the past
$this->entity_mapping->update_mapping($mapping_id, [
'last_sync_perfex' => date('Y-m-d H:i:s', strtotime('-1 hour')),
'last_sync_moloni' => date('Y-m-d H:i:s', strtotime('-1 hour'))
]);
// Mock Perfex client with recent changes
$modified_client_data = $this->test_client_data;
$modified_client_data['company'] = 'Modified Company Name';
$this->mockPerfexClientRetrieval($perfex_client_id, $modified_client_data);
// Mock Moloni customer with different recent changes
$modified_moloni_data = $this->test_moloni_data;
$modified_moloni_data['name'] = 'Different Modified Name';
$this->api_client_mock->expects($this->once())
->method('get_customer')
->with($moloni_customer_id)
->willReturn([
'success' => true,
'data' => $modified_moloni_data
]);
// Mock modification time methods
$this->mockModificationTimes();
// Act
$result = $this->client_sync->sync_perfex_to_moloni($perfex_client_id);
// Assert conflict detected
$this->assertFalse($result['success']);
$this->assertArrayHasKey('conflict_details', $result);
$this->assertTrue($result['requires_manual_resolution']);
// Verify mapping status updated to conflict
$updated_mapping = $this->entity_mapping->get_mapping(
EntityMappingService::ENTITY_CUSTOMER,
$perfex_client_id,
$moloni_customer_id
);
$this->assertEquals(EntityMappingService::STATUS_CONFLICT, $updated_mapping->sync_status);
}
/**
* Test error handling for API failures
*/
public function test_error_handling_for_api_failures()
{
// Arrange
$perfex_client_id = $this->test_client_data['userid'];
$this->mockPerfexClientRetrieval($perfex_client_id, $this->test_client_data);
// Mock API failure
$this->api_client_mock->expects($this->once())
->method('create_customer')
->willReturn([
'success' => false,
'message' => 'API authentication failed'
]);
// Act
$result = $this->client_sync->sync_perfex_to_moloni($perfex_client_id);
// Assert
$this->assertFalse($result['success']);
$this->assertStringContains('Moloni API error', $result['message']);
$this->assertGreaterThan(0, $result['execution_time']);
}
/**
* Test customer matching functionality
*/
public function test_customer_matching_functionality()
{
// Arrange
$search_data = [
'company' => 'Test Company Ltd',
'vat' => 'PT123456789',
'email' => 'test@testcompany.com'
];
// Mock API search responses
$this->api_client_mock->expects($this->once())
->method('search_customers')
->with(['vat' => $search_data['vat']])
->willReturn([
'success' => true,
'data' => [
[
'customer_id' => 888,
'name' => $search_data['company'],
'vat' => $search_data['vat']
]
]
]);
// Act
$matches = $this->client_sync->find_moloni_customer_matches($search_data);
// Assert
$this->assertIsArray($matches);
$this->assertNotEmpty($matches);
$this->assertEquals(ClientSyncService::MATCH_SCORE_EXACT, $matches[0]['match_score']);
$this->assertEquals('vat', $matches[0]['match_type']);
}
/**
* Test batch sync functionality
*/
public function test_batch_sync_functionality()
{
// Arrange
$client_ids = [100, 101, 102];
foreach ($client_ids as $client_id) {
$client_data = $this->test_client_data;
$client_data['userid'] = $client_id;
$this->mockPerfexClientRetrieval($client_id, $client_data);
}
// Mock successful API responses
$this->api_client_mock->expects($this->exactly(3))
->method('create_customer')
->willReturn([
'success' => true,
'data' => ['customer_id' => 999]
]);
// Act
$result = $this->client_sync->batch_sync_customers($client_ids);
// Assert
$this->assertIsArray($result);
$this->assertEquals(3, $result['total']);
$this->assertEquals(3, $result['success']);
$this->assertEquals(0, $result['errors']);
$this->assertCount(3, $result['details']);
}
/**
* Test sync statistics tracking
*/
public function test_sync_statistics_tracking()
{
// Arrange - Perform several sync operations
$this->setupMultipleSyncOperations();
// Act
$stats = $this->client_sync->get_sync_statistics();
// Assert
$this->assertIsArray($stats);
$this->assertArrayHasKey('total_customers', $stats);
$this->assertArrayHasKey('synced_customers', $stats);
$this->assertArrayHasKey('pending_customers', $stats);
$this->assertArrayHasKey('error_customers', $stats);
$this->assertArrayHasKey('last_sync', $stats);
}
// Helper methods
protected function mockPerfexClientRetrieval($client_id, $client_data)
{
// Mock CodeIgniter instance and clients_model
$CI = $this->createMock(stdClass::class);
$clients_model = $this->createMock(stdClass::class);
$clients_model->expects($this->any())
->method('get')
->with($client_id)
->willReturn((object)$client_data);
// This would need proper CI mock injection in real implementation
}
protected function mockPerfexClientCreation($expected_client_id)
{
// Mock successful client creation in Perfex
// This would need proper CI mock injection in real implementation
}
protected function mockModificationTimes()
{
// Mock modification time retrieval methods
$reflection = new ReflectionClass($this->client_sync);
$perfex_time_method = $reflection->getMethod('get_perfex_modification_time');
$perfex_time_method->setAccessible(true);
$moloni_time_method = $reflection->getMethod('get_moloni_modification_time');
$moloni_time_method->setAccessible(true);
// Set times to simulate recent modifications on both sides
// Implementation would need proper mocking
}
protected function setupMultipleSyncOperations()
{
// Setup multiple test sync operations for statistics testing
// This would involve creating multiple mappings and sync logs
}
protected function tearDown(): void
{
// Clean up test data
$this->cleanupTestData();
parent::tearDown();
}
protected function cleanupTestData()
{
// Remove test mappings and sync logs
// This would need proper database cleanup
}
}