bemade-addons/odoo_to_odoo_sync/tests/test_field_mapping.py
mathis 4d92c5a72f [IMP] odoo_to_odoo_sync: implement dependency management, fix connection errors, and add field mapping
- Added comprehensive dependency management with proper OdooRPC library integration
- Fixed XML-RPC, JSON-RPC, and OdooRPC connection authentication issues
- Implemented proper Odoo 18 API key format support with scope parameter
- Added intelligent field mapping wizard with Full/Required/Balanced modes
- Enhanced error handling and connection state management
- Removed legacy API key system for clean reinstall capability
- Added detailed debug logging for connection troubleshooting
2025-08-15 08:40:48 -04:00

355 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Test Suite for Advanced Field Mapping in Odoo Sync Module
This module tests all 4 mapping types:
- direct: basic field-to-field mapping
- function: transformation via Python methods
- computed: Python expression evaluation
- relation: cross-model lookups
"""
import json
from unittest.mock import patch, MagicMock
from odoo.tests import common
from odoo.exceptions import ValidationError
class TestFieldMapping(common.TransactionCase):
"""Test cases for advanced field mapping functionality."""
def setUp(self):
"""Set up test environment."""
super(TestFieldMapping, self).setUp()
# Get required models
self.SyncModel = self.env['odoo.sync.model']
self.SyncField = self.env['odoo.sync.model.field']
self.SyncManager = self.env['odoo.sync.manager']
self.Partner = self.env['res.partner']
self.Country = self.env['res.country']
# Create test data
self.test_country = self.Country.create({
'name': 'United States',
'code': 'US'
})
# Create test partner
self.test_partner = self.Partner.create({
'name': 'Test Partner',
'email': 'test@example.com',
'phone': '+1234567890'
})
# Create sync model
self.sync_model = self.SyncModel.create({
'name': 'Test Partner Sync',
'model_id': self.env['ir.model'].search([('model', '=', 'res.partner')], limit=1).id,
'target_model': 'res.partner',
'active': True
})
def test_direct_mapping(self):
"""Test direct field mapping."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'name')
], limit=1).id,
'mapping_type': 'direct',
'source_field': 'name',
'target_field': 'display_name',
'active': True
})
# Test direct mapping
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'Test Partner'
)
self.assertEqual(result, 'Test Partner', "Direct mapping should return value as-is")
def test_function_mapping(self):
"""Test function-based field mapping."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'name')
], limit=1).id,
'mapping_type': 'function',
'mapping_function': 'upper',
'active': True
})
# Test function mapping
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'test value'
)
self.assertEqual(result, 'TEST VALUE', "Function mapping should apply upper()")
def test_computed_mapping(self):
"""Test computed field mapping with expressions."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'name')
], limit=1).id,
'mapping_type': 'computed',
'mapping_expression': 'record.name.upper()',
'active': True
})
# Test computed mapping
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'test partner'
)
self.assertEqual(result, 'TEST PARTNER', "Computed mapping should apply expression")
def test_relation_mapping(self):
"""Test relation mapping with cross-model lookups."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'country_id')
], limit=1).id,
'mapping_type': 'relation',
'relation_model': 'res.country',
'relation_field': 'code',
'relation_domain': '[("active", "=", True)]',
'active': True
})
# Test relation mapping
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'US'
)
self.assertEqual(result, self.test_country.id,
"Relation mapping should return country ID")
def test_relation_mapping_no_match(self):
"""Test relation mapping when no match found."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'country_id')
], limit=1).id,
'mapping_type': 'relation',
'relation_model': 'res.country',
'relation_field': 'code',
'active': True
})
# Test with non-existent country code
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'XX'
)
self.assertIsNone(result, "Should return None for no match")
def test_invalid_mapping_type(self):
"""Test handling of invalid mapping type."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'name')
], limit=1).id,
'mapping_type': 'invalid_type',
'active': True
})
# Should handle invalid type gracefully
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'test value'
)
self.assertEqual(result, 'test value',
"Invalid mapping type should return original value")
def test_function_mapping_error(self):
"""Test error handling in function mapping."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'name')
], limit=1).id,
'mapping_type': 'function',
'mapping_function': 'non_existent_method',
'active': True
})
# Should handle function errors gracefully
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'test value'
)
self.assertEqual(result, 'test value',
"Function errors should return original value")
def test_computed_mapping_error(self):
"""Test error handling in computed mapping."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'name')
], limit=1).id,
'mapping_type': 'computed',
'mapping_expression': 'invalid python syntax [',
'active': True
})
# Should handle expression errors gracefully
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'test value'
)
self.assertEqual(result, 'test value',
"Expression errors should return original value")
def test_required_field_validation(self):
"""Test required field handling."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'name')
], limit=1).id,
'mapping_type': 'relation',
'relation_model': 'res.country',
'relation_field': 'code',
'required': True,
'active': True
})
# Should raise exception for required field with no match
with self.assertRaises(Exception):
self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'XX' # Non-existent country
)
def test_complex_computed_mapping(self):
"""Test complex computed field expressions."""
# Update partner with more fields
self.test_partner.write({
'firstname': 'John',
'lastname': 'Doe'
})
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'name')
], limit=1).id,
'mapping_type': 'computed',
'mapping_expression': 'record.firstname + " " + record.lastname',
'active': True
})
# Test complex expression
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'placeholder'
)
self.assertEqual(result, 'John Doe',
"Complex computed mapping should work")
def test_json_domain_parsing(self):
"""Test JSON domain parsing in relation mapping."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'country_id')
], limit=1).id,
'mapping_type': 'relation',
'relation_model': 'res.country',
'relation_field': 'code',
'relation_domain': json.dumps([('active', '=', True)]),
'active': True
})
# Test with valid JSON domain
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'US'
)
self.assertEqual(result, self.test_country.id,
"JSON domain parsing should work")
def test_invalid_json_domain(self):
"""Test handling of invalid JSON domain."""
field_mapping = self.SyncField.create({
'model_sync_id': self.sync_model.id,
'field_id': self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'country_id')
], limit=1).id,
'mapping_type': 'relation',
'relation_model': 'res.country',
'relation_field': 'code',
'relation_domain': 'invalid json {',
'active': True
})
# Should handle invalid JSON gracefully
result = self.SyncManager._apply_field_mapping(
field_mapping,
self.test_partner,
'US'
)
self.assertEqual(result, self.test_country.id,
"Invalid JSON domain should not break mapping")
class TestFieldMappingIntegration(common.TransactionCase):
"""Integration tests for field mapping with sync process."""
def setUp(self):
super(TestFieldMappingIntegration, self).setUp()
# Setup for integration tests
self.sync_manager = self.env['odoo.sync.manager'].create({
'name': 'Test Sync Manager'
})
def test_prepare_sync_data_with_mappings(self):
"""Test _prepare_sync_data with field mappings."""
# This would test the full sync data preparation
# Implementation depends on sync queue structure
pass
def test_end_to_end_sync_with_mappings(self):
"""Test complete sync process with all mapping types."""
# This would test the full sync flow
# Implementation depends on sync instance setup
pass
if __name__ == '__main__':
# Run tests
import unittest
unittest.main()