|
| 1 | +from types import SimpleNamespace |
| 2 | +from unittest.mock import MagicMock |
| 3 | + |
1 | 4 | from odoo.exceptions import UserError, ValidationError |
2 | 5 | from odoo.tests.common import TransactionCase |
3 | 6 |
|
@@ -786,3 +789,85 @@ def test_uri_updates_on_code_change(self): |
786 | 789 | # URI should be recomputed |
787 | 790 | self.assertEqual(code.uri, "urn:test:user#CHANGED") |
788 | 791 | self.assertNotEqual(code.uri, original_uri) |
| 792 | + |
| 793 | + # Import name_search scoping tests |
| 794 | + def _create_shared_display_codes(self, display="Active"): |
| 795 | + """Helper: create a code with the same display in two different vocabularies.""" |
| 796 | + code_user = self.VocabularyCode.create( |
| 797 | + {"vocabulary_id": self.vocab_user.id, "code": "NS_ACT1", "display": display} |
| 798 | + ) |
| 799 | + code_system = self.VocabularyCode.with_context(_test_bypass_system_protection=True).create( |
| 800 | + {"vocabulary_id": self.vocab_system.id, "code": "NS_ACT2", "display": display} |
| 801 | + ) |
| 802 | + return code_user, code_system |
| 803 | + |
| 804 | + def test_name_search_without_context_returns_all_matches(self): |
| 805 | + """Without context domain, name_search returns codes from all vocabularies.""" |
| 806 | + code_user, code_system = self._create_shared_display_codes("SharedDisplay") |
| 807 | + results = self.VocabularyCode.name_search("SharedDisplay", operator="=") |
| 808 | + result_ids = [r[0] for r in results] |
| 809 | + self.assertIn(code_user.id, result_ids) |
| 810 | + self.assertIn(code_system.id, result_ids) |
| 811 | + |
| 812 | + def test_name_search_with_context_domain_scopes_to_vocabulary(self): |
| 813 | + """With _import_name_search_domain in context, name_search is restricted to that vocabulary.""" |
| 814 | + code_user, code_system = self._create_shared_display_codes("ScopedDisplay") |
| 815 | + domain = [("vocabulary_id", "=", self.vocab_user.id)] |
| 816 | + results = self.VocabularyCode.with_context(_import_name_search_domain=domain).name_search( |
| 817 | + "ScopedDisplay", operator="=" |
| 818 | + ) |
| 819 | + result_ids = [r[0] for r in results] |
| 820 | + self.assertIn(code_user.id, result_ids) |
| 821 | + self.assertNotIn(code_system.id, result_ids) |
| 822 | + |
| 823 | + def test_name_search_context_domain_does_not_affect_other_vocabulary(self): |
| 824 | + """Context domain scoped to system vocab excludes user vocab codes.""" |
| 825 | + code_user, code_system = self._create_shared_display_codes("OtherScopeDisplay") |
| 826 | + domain = [("vocabulary_id", "=", self.vocab_system.id)] |
| 827 | + results = self.VocabularyCode.with_context(_import_name_search_domain=domain).name_search( |
| 828 | + "OtherScopeDisplay", operator="=" |
| 829 | + ) |
| 830 | + result_ids = [r[0] for r in results] |
| 831 | + self.assertIn(code_system.id, result_ids) |
| 832 | + self.assertNotIn(code_user.id, result_ids) |
| 833 | + |
| 834 | + def test_db_id_for_list_domain_scopes_name_search(self): |
| 835 | + """db_id_for passes a list field domain to name_search, avoiding cross-vocab matches.""" |
| 836 | + code_user, code_system = self._create_shared_display_codes("DbIdDisplay") |
| 837 | + field = SimpleNamespace( |
| 838 | + domain=[("vocabulary_id", "=", self.vocab_user.id)], |
| 839 | + comodel_name="spp.vocabulary.code", |
| 840 | + ) |
| 841 | + savepoint = MagicMock() |
| 842 | + converter = self.env["ir.fields.converter"] |
| 843 | + result_id, warnings = converter.db_id_for(None, field, None, "DbIdDisplay", savepoint) |
| 844 | + self.assertEqual(result_id, code_user.id) |
| 845 | + self.assertEqual(warnings, []) |
| 846 | + |
| 847 | + def test_db_id_for_string_domain_scopes_name_search(self): |
| 848 | + """db_id_for evaluates a static string domain and applies it to name_search.""" |
| 849 | + code_user, code_system = self._create_shared_display_codes("StrDomainDisplay") |
| 850 | + domain_str = f"[('vocabulary_id', '=', {self.vocab_user.id})]" |
| 851 | + field = SimpleNamespace( |
| 852 | + domain=domain_str, |
| 853 | + comodel_name="spp.vocabulary.code", |
| 854 | + ) |
| 855 | + savepoint = MagicMock() |
| 856 | + converter = self.env["ir.fields.converter"] |
| 857 | + result_id, warnings = converter.db_id_for(None, field, None, "StrDomainDisplay", savepoint) |
| 858 | + self.assertEqual(result_id, code_user.id) |
| 859 | + self.assertEqual(warnings, []) |
| 860 | + |
| 861 | + def test_db_id_for_no_domain_returns_multiple_match_warning(self): |
| 862 | + """db_id_for with no field domain falls back to unscoped search, producing a multiple-match warning.""" |
| 863 | + code_user, code_system = self._create_shared_display_codes("NoDomainDisplay") |
| 864 | + field = SimpleNamespace( |
| 865 | + domain=[], |
| 866 | + comodel_name="spp.vocabulary.code", |
| 867 | + ) |
| 868 | + savepoint = MagicMock() |
| 869 | + converter = self.env["ir.fields.converter"] |
| 870 | + result_id, warnings = converter.db_id_for(None, field, None, "NoDomainDisplay", savepoint) |
| 871 | + # Still resolves (picks first), but warns about multiple matches |
| 872 | + self.assertIsNotNone(result_id) |
| 873 | + self.assertTrue(len(warnings) > 0) |
0 commit comments