entity_mapping_mock = $this->createMock(EntityMappingService::class); $this->api_client_mock = $this->createMock(MoloniApiClient::class); $this->error_handler_mock = $this->createMock(ErrorHandler::class); // Mock CodeIgniter instance $this->CI_mock = $this->createMock(stdClass::class); $this->CI_mock->clients_model = $this->createMock(stdClass::class); $this->CI_mock->desk_moloni_model = $this->createMock(stdClass::class); // Initialize service with mocked dependencies $this->client_sync_service = new ClientSyncService(); // Use reflection to inject mocks $reflection = new ReflectionClass($this->client_sync_service); $entity_mapping_property = $reflection->getProperty('entity_mapping'); $entity_mapping_property->setAccessible(true); $entity_mapping_property->setValue($this->client_sync_service, $this->entity_mapping_mock); $api_client_property = $reflection->getProperty('api_client'); $api_client_property->setAccessible(true); $api_client_property->setValue($this->client_sync_service, $this->api_client_mock); $error_handler_property = $reflection->getProperty('error_handler'); $error_handler_property->setAccessible(true); $error_handler_property->setValue($this->client_sync_service, $this->error_handler_mock); $ci_property = $reflection->getProperty('CI'); $ci_property->setAccessible(true); $ci_property->setValue($this->client_sync_service, $this->CI_mock); } public function testSyncPerfexToMoloniSuccess() { // Test data $perfex_client_id = 123; $perfex_client = [ 'userid' => 123, 'company' => 'Test Company Ltd', 'vat' => 'PT123456789', 'email' => 'test@example.com', 'phonenumber' => '+351912345678', 'billing_street' => 'Test Street 123', 'billing_city' => 'Porto', 'billing_zip' => '4000-001', 'billing_country' => 'PT' ]; $moloni_customer_data = [ 'name' => 'Test Company Ltd', 'vat' => 'PT123456789', 'email' => 'test@example.com', 'phone' => '+351912345678', 'address' => 'Test Street 123', 'city' => 'Porto', 'zip_code' => '4000-001', 'country_id' => 1 ]; // Mock no existing mapping $this->entity_mapping_mock ->expects($this->once()) ->method('get_mapping_by_perfex_id') ->with(EntityMappingService::ENTITY_CUSTOMER, $perfex_client_id) ->willReturn(null); // Mock successful Perfex client retrieval $this->CI_mock->clients_model ->expects($this->once()) ->method('get') ->with($perfex_client_id) ->willReturn((object)$perfex_client); // Mock successful Moloni API call $this->api_client_mock ->expects($this->once()) ->method('create_customer') ->with($this->callback(function($data) use ($moloni_customer_data) { return $data['name'] === $moloni_customer_data['name'] && $data['vat'] === $moloni_customer_data['vat'] && $data['email'] === $moloni_customer_data['email']; })) ->willReturn([ 'success' => true, 'data' => ['customer_id' => 456] ]); // Mock mapping creation $this->entity_mapping_mock ->expects($this->once()) ->method('create_mapping') ->with( EntityMappingService::ENTITY_CUSTOMER, $perfex_client_id, 456, EntityMappingService::DIRECTION_PERFEX_TO_MOLONI ) ->willReturn(1); // Mock activity logging $this->CI_mock->desk_moloni_model ->expects($this->once()) ->method('log_sync_activity') ->with($this->callback(function($data) { return $data['entity_type'] === 'customer' && $data['action'] === 'create' && $data['direction'] === 'perfex_to_moloni' && $data['status'] === 'success'; })); // Execute test $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id); // Assertions $this->assertTrue($result['success']); $this->assertEquals('Customer created successfully in Moloni', $result['message']); $this->assertEquals(1, $result['mapping_id']); $this->assertEquals(456, $result['moloni_customer_id']); $this->assertEquals('create', $result['action']); $this->assertArrayHasKey('execution_time', $result); } public function testSyncMoloniToPerfexSuccess() { // Test data $moloni_customer_id = 456; $moloni_customer = [ 'customer_id' => 456, 'name' => 'Test Company Ltd', 'vat' => 'PT123456789', 'email' => 'test@example.com', 'phone' => '+351912345678', 'address' => 'Test Street 123', 'city' => 'Porto', 'zip_code' => '4000-001', 'country_id' => 1 ]; $perfex_client_data = [ 'company' => 'Test Company Ltd', 'vat' => 'PT123456789', 'email' => 'test@example.com', 'phonenumber' => '+351912345678', 'billing_street' => 'Test Street 123', 'billing_city' => 'Porto', 'billing_zip' => '4000-001', 'billing_country' => 'PT' ]; // Mock no existing mapping $this->entity_mapping_mock ->expects($this->once()) ->method('get_mapping_by_moloni_id') ->with(EntityMappingService::ENTITY_CUSTOMER, $moloni_customer_id) ->willReturn(null); // Mock successful Moloni customer retrieval $this->api_client_mock ->expects($this->once()) ->method('get_customer') ->with($moloni_customer_id) ->willReturn([ 'success' => true, 'data' => $moloni_customer ]); // Mock successful Perfex client creation $this->CI_mock->clients_model ->expects($this->once()) ->method('add') ->with($this->callback(function($data) use ($perfex_client_data) { return $data['company'] === $perfex_client_data['company'] && $data['vat'] === $perfex_client_data['vat'] && $data['email'] === $perfex_client_data['email']; })) ->willReturn(123); // Mock mapping creation $this->entity_mapping_mock ->expects($this->once()) ->method('create_mapping') ->with( EntityMappingService::ENTITY_CUSTOMER, 123, $moloni_customer_id, EntityMappingService::DIRECTION_MOLONI_TO_PERFEX ) ->willReturn(1); // Execute test $result = $this->client_sync_service->sync_moloni_to_perfex($moloni_customer_id); // Assertions $this->assertTrue($result['success']); $this->assertEquals('Customer created successfully in Perfex', $result['message']); $this->assertEquals(1, $result['mapping_id']); $this->assertEquals(123, $result['perfex_client_id']); $this->assertEquals('create', $result['action']); } public function testSyncPerfexToMoloniWithConflict() { // Test data $perfex_client_id = 123; $mapping = (object)[ 'id' => 1, 'perfex_id' => 123, 'moloni_id' => 456, 'last_sync_perfex' => '2024-01-01 10:00:00', 'last_sync_moloni' => '2024-01-01 09:00:00' ]; $perfex_client = [ 'userid' => 123, 'company' => 'Test Company Ltd - Updated', 'vat' => 'PT123456789', 'email' => 'test@example.com' ]; $moloni_customer = [ 'customer_id' => 456, 'name' => 'Test Company Ltd - Different Update', 'vat' => 'PT123456789', 'email' => 'test@example.com' ]; // Mock existing mapping $this->entity_mapping_mock ->expects($this->once()) ->method('get_mapping_by_perfex_id') ->with(EntityMappingService::ENTITY_CUSTOMER, $perfex_client_id) ->willReturn($mapping); // Mock Perfex client retrieval $this->CI_mock->clients_model ->expects($this->once()) ->method('get') ->with($perfex_client_id) ->willReturn((object)$perfex_client); // Mock Moloni customer retrieval for conflict check $this->api_client_mock ->expects($this->once()) ->method('get_customer') ->with(456) ->willReturn([ 'success' => true, 'data' => $moloni_customer ]); // Mock mapping status update to conflict $this->entity_mapping_mock ->expects($this->once()) ->method('update_mapping_status') ->with( 1, EntityMappingService::STATUS_CONFLICT, $this->isType('string') ); // Execute test $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id); // Assertions $this->assertFalse($result['success']); $this->assertStringContains('conflict', strtolower($result['message'])); $this->assertArrayHasKey('conflict_details', $result); $this->assertTrue($result['requires_manual_resolution']); } public function testFindMoloniCustomerMatches() { // Test data $perfex_client = [ 'company' => 'Test Company Ltd', 'vat' => 'PT123456789', 'email' => 'test@example.com', 'phonenumber' => '+351912345678' ]; $moloni_matches = [ [ 'customer_id' => 456, 'name' => 'Test Company Ltd', 'vat' => 'PT123456789', 'email' => 'test@example.com' ] ]; // Mock VAT search returning exact match $this->api_client_mock ->expects($this->once()) ->method('search_customers') ->with(['vat' => 'PT123456789']) ->willReturn([ 'success' => true, 'data' => $moloni_matches ]); // Execute test $matches = $this->client_sync_service->find_moloni_customer_matches($perfex_client); // Assertions $this->assertCount(1, $matches); $this->assertEquals(100, $matches[0]['match_score']); // Exact match $this->assertEquals('vat', $matches[0]['match_type']); $this->assertEquals(['vat' => 'PT123456789'], $matches[0]['match_criteria']); } public function testSyncPerfexToMoloniWithMissingClient() { // Test data $perfex_client_id = 999; // Non-existent client // Mock no existing mapping $this->entity_mapping_mock ->expects($this->once()) ->method('get_mapping_by_perfex_id') ->with(EntityMappingService::ENTITY_CUSTOMER, $perfex_client_id) ->willReturn(null); // Mock client not found $this->CI_mock->clients_model ->expects($this->once()) ->method('get') ->with($perfex_client_id) ->willReturn(null); // Mock error logging $this->error_handler_mock ->expects($this->once()) ->method('log_error') ->with('sync', 'CLIENT_SYNC_FAILED', $this->stringContains('not found')); // Execute test $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id); // Assertions $this->assertFalse($result['success']); $this->assertStringContains('not found', $result['message']); $this->assertArrayHasKey('execution_time', $result); } public function testSyncPerfexToMoloniWithApiError() { // Test data $perfex_client_id = 123; $perfex_client = [ 'userid' => 123, 'company' => 'Test Company Ltd', 'vat' => 'PT123456789', 'email' => 'test@example.com' ]; // Mock no existing mapping $this->entity_mapping_mock ->expects($this->once()) ->method('get_mapping_by_perfex_id') ->willReturn(null); // Mock successful Perfex client retrieval $this->CI_mock->clients_model ->expects($this->once()) ->method('get') ->willReturn((object)$perfex_client); // Mock Moloni API error $this->api_client_mock ->expects($this->once()) ->method('create_customer') ->willReturn([ 'success' => false, 'message' => 'Moloni API connection failed' ]); // Mock error logging $this->error_handler_mock ->expects($this->once()) ->method('log_error') ->with('sync', 'CLIENT_SYNC_FAILED', $this->stringContains('Moloni API')); // Execute test $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id); // Assertions $this->assertFalse($result['success']); $this->assertStringContains('Moloni API', $result['message']); } public function testClientUpdateWithSignificantChanges() { // Test data reflecting significant field changes $perfex_client_id = 123; $original_client = [ 'userid' => 123, 'company' => 'Test Company Ltd', 'vat' => 'PT123456789', 'email' => 'old@example.com' ]; $updated_client = [ 'userid' => 123, 'company' => 'Test Company Ltd', 'vat' => 'PT123456789', 'email' => 'new@example.com' // Significant change ]; $mapping = (object)[ 'id' => 1, 'perfex_id' => 123, 'moloni_id' => 456, 'sync_status' => EntityMappingService::STATUS_SYNCED ]; // Mock existing mapping $this->entity_mapping_mock ->expects($this->once()) ->method('get_mapping_by_perfex_id') ->willReturn($mapping); // Mock updated client data $this->CI_mock->clients_model ->expects($this->once()) ->method('get') ->willReturn((object)$updated_client); // Mock successful update $this->api_client_mock ->expects($this->once()) ->method('update_customer') ->willReturn([ 'success' => true, 'data' => ['customer_id' => 456] ]); // Mock mapping update $this->entity_mapping_mock ->expects($this->once()) ->method('update_mapping'); // Execute test $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id, true); // Assertions $this->assertTrue($result['success']); $this->assertEquals('update', $result['action']); $this->assertArrayHasKey('data_changes', $result); } protected function tearDown(): void { // Clean up any test artifacts $this->client_sync_service = null; $this->entity_mapping_mock = null; $this->api_client_mock = null; $this->error_handler_mock = null; $this->CI_mock = null; } }