1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2026 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Tests for the sanitizer that removes multilingual concatenated names.
12 from nominatim_db.config import Configuration
13 from nominatim_db.data.place_info import PlaceInfo
14 from nominatim_db.tokenizer.place_sanitizer import PlaceSanitizer
18 def run_sanitizer(def_config):
19 def _run(extra_args=None, **kwargs):
20 place = PlaceInfo({'name': {k.replace('_', ':'): v
21 for k, v in kwargs.items()},
22 'country_code': 'be', 'rank_address': 26})
24 sanitizer_args = {'step': 'clean-multilingual-names'}
26 sanitizer_args.update(extra_args)
28 PlaceSanitizer([sanitizer_args], def_config).process_names(place)
30 return sorted([(p.name, p.kind, p.suffix or '')
31 for p in place.searchable_names])
37 """ Test removal with some common delimiters used worlwide.
40 def test_hyphen_delimiter(self, run_sanitizer):
41 res = run_sanitizer(name='गढ़वा - Garhwa - گڑھوا',
46 assert ('गढ़वा - Garhwa - گڑھوا', 'name', '') not in res
47 assert ('गढ़वा', 'name', 'hi') in res
48 assert ('Garhwa', 'name', 'en') in res
49 assert ('گڑھوا', 'name', 'ur') in res
51 def test_slash_delimiter(self, run_sanitizer):
52 res = run_sanitizer(name='Rue de la Gare / Stationsstraat/Bahnhofstraße',
53 name_fr='Rue de la Gare',
54 name_nl='Stationsstraat',
55 name_de='Bahnhofstraße')
57 assert ('Rue de la Gare / Stationsstraat/Bahnhofstraße', 'name', '') not in res
58 assert ('Rue de la Gare', 'name', 'fr') in res
59 assert ('Stationsstraat', 'name', 'nl') in res
60 assert ('Bahnhofstraße', 'name', 'de') in res
62 def test_space_delimiter(self, run_sanitizer):
63 res = run_sanitizer(name='Oran ⵡⴻⵀⵔⴰⵏ وهران',
69 assert ('Oran ⵡⴻⵀⵔⴰⵏ وهران', 'name', '') not in res
70 assert ('Oran', 'name', 'en') in res
71 assert ('ⵡⴻⵀⵔⴰⵏ', 'name', 'ber') in res
72 assert ('وهران', 'name', 'ar') in res
73 assert ('Аран', 'name', 'be') in res
75 def test_space_delimiter_multiword_name(self, run_sanitizer):
76 res = run_sanitizer(name='Baarin Left Banner ᠪᠠᠭᠠᠷᠢᠨ ᠵᠡᠭᠦᠨ 巴林左旗',
77 name_en='Baarin Left Banner',
78 name_mn='ᠪᠠᠭᠠᠷᠢᠨ ᠵᠡᠭᠦᠨ',
81 assert ('Baarin Left Banner ᠪᠠᠭᠠᠷᠢᠨ ᠵᠡᠭᠦᠨ 巴林左旗', 'name', '') not in res
82 assert ('Baarin Left Banner', 'name', 'en') in res
83 assert ('ᠪᠠᠭᠠᠷᠢᠨ ᠵᠡᠭᠦᠨ', 'name', 'mn') in res
84 assert ('巴林左旗', 'name', 'zh') in res
86 def test_mixed_delimiters(self, run_sanitizer):
87 res = run_sanitizer(name='गढ़वा /Garhwa - گڑھوا',
92 assert ('गढ़वा /Garhwa - گڑھوا', 'name', '') not in res
94 def test_name_equals_single_lang_name(self, run_sanitizer):
95 res = run_sanitizer(name='New York City',
96 name_en='New York City',
97 name_fr='Ville de New York')
99 assert ('New York City', 'name', '') not in res
101 def test_name_does_not_match_any_pattern(self, run_sanitizer):
102 res = run_sanitizer(name='Some Unique Name',
103 name_fr='Rue du Poulet',
104 name_nl='Kiekenstraat')
106 assert ('Some Unique Name', 'name', '') in res
108 def test_partial_match(self, run_sanitizer):
109 res = run_sanitizer(name='Rue du Poulet - Unknown',
110 name_fr='Rue du Poulet',
111 name_nl='Kiekenstraat')
113 assert ('Rue du Poulet - Unknown', 'name', '') in res
115 def test_custom_delimiter(self, run_sanitizer):
116 res = run_sanitizer(extra_args={'delimiters': ['|']},
117 name='गढ़वा|Garhwa | گڑھوا',
122 assert ('गढ़वा|Garhwa | گڑھوا', 'name', '') not in res
124 def test_delimeter_in_name(self, run_sanitizer):
125 res = run_sanitizer(name='Berchem-Sainte-Agathe - Sint-Agatha-Berchem',
126 name_ar='بيرشيم سانت أغاث',
127 name_fr='Berchem-Sainte-Agathe',
128 name_la='Berchemium Agathae',
129 name_li='Sint-Agatha-Berchem',
130 name_nl='Sint-Agatha-Berchem',
131 name_ru='Беркем-Сент-Агат')
133 assert ('Berchem-Sainte-Agathe - Sint-Agatha-Berchem', 'name', '') not in res
134 assert ('بيرشيم سانت أغاث', 'name', 'ar') in res
135 assert ('Berchem-Sainte-Agathe', 'name', 'fr') in res
136 assert ('Berchemium Agathae', 'name', 'la') in res
137 assert ('Sint-Agatha-Berchem', 'name', 'li') in res
138 assert ('Sint-Agatha-Berchem', 'name', 'nl') in res
139 assert ('Беркем-Сент-Агат', 'name', 'ru') in res
141 def test_leading_or_trailing_delimiter(self, run_sanitizer):
142 res = run_sanitizer(name='/ Foo - Bar - ',
146 assert ('/ Foo - Bar -', 'name', '') not in res
147 assert ('Foo', 'name', 'fr') in res
148 assert ('Bar', 'name', 'es') in res
150 def test_longest_match_priority(self, run_sanitizer):
151 """ Test that overlapping substrings in language names do not break
152 the concatenation check due to premature regex matching.
154 res = run_sanitizer(name='New - New York',
158 assert ('New - New York', 'name', '') not in res
159 assert ('New', 'name', 'fr') in res
160 assert ('New York', 'name', 'en') in res
162 def test_non_language_suffix_ignored(self, run_sanitizer):
163 res = run_sanitizer(name='Old Town Hall',
164 name_prefix='Old Town',
167 assert ('Old Town Hall', 'name', '') in res
169 def test_three_letter_language_used(self, run_sanitizer):
170 # A language code followed by a script variant is a valid suffix.
171 res = run_sanitizer(name='臺北 - Taipei',
175 assert ('臺北 - Taipei', 'name', '') not in res
178 class TestFilterKind:
179 """ Test with custom filter-kind configuration.
182 def test_filter_kind_ref(self, run_sanitizer):
183 place = PlaceInfo({'name': {'name': 'गढ़वा - Garhwa',
189 'country_code': 'be', 'rank_address': 26})
191 # When filter-kind is set to 'ref', only ref names are processed.
192 cfg = Configuration(None)
193 PlaceSanitizer([{'step': 'clean-multilingual-names',
194 'filter-kind': 'ref'}], cfg).process_names(place)
196 names = [(p.name, p.kind, p.suffix or '') for p in place.searchable_names]
197 # 'name' bare tag should be kept (not matched by filter-kind)
198 assert ('गढ़वा - Garhwa', 'name', '') in names
199 # 'ref' bare tag should be removed
200 assert ('A - B', 'ref', '') not in names
202 def test_alt_name_not_affected_by_default(self, run_sanitizer):
203 place = PlaceInfo({'name': {'name': 'गढ़वा - Garhwa',
206 'alt_name': 'Baz - Qux',
207 'alt_name:fr': 'Baz',
208 'alt_name:nl': 'Qux'},
209 'country_code': 'be', 'rank_address': 26})
211 cfg = Configuration(None)
212 PlaceSanitizer([{'step': 'clean-multilingual-names'}], cfg).process_names(place)
214 names = [(p.name, p.kind, p.suffix or '') for p in place.searchable_names]
215 assert ('गढ़वा - Garhwa', 'name', '') not in names
216 assert ('गढ़वा', 'name', 'fr') in names
217 assert ('Garhwa', 'name', 'nl') in names
218 # alt_name kind, doesn't match default filter-kind='name'.
219 assert ('Baz - Qux', 'alt_name', '') in names
220 assert ('Baz', 'alt_name', 'fr') in names
221 assert ('Qux', 'alt_name', 'nl') in names