]> git.openstreetmap.org Git - nominatim.git/blob - test/python/api/test_result_formatting_v1_reverse.py
Locales and localization refactor with Locales as a localizer object.
[nominatim.git] / test / python / api / test_result_formatting_v1_reverse.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2025 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Tests for formatting reverse results for the V1 API.
9
10 These test only ensure that the Python code is correct.
11 For functional tests see BDD test suite.
12 """
13 import json
14 import xml.etree.ElementTree as ET
15
16 import pytest
17
18 from nominatim_api.v1.format import dispatch as v1_format
19 import nominatim_api as napi
20
21 FORMATS = ['json', 'jsonv2', 'geojson', 'geocodejson', 'xml']
22
23
24 @pytest.mark.parametrize('fmt', FORMATS)
25 def test_format_reverse_minimal(fmt):
26     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
27                                  ('amenity', 'post_box'),
28                                  napi.Point(0.3, -8.9))
29
30     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {})
31
32     if fmt == 'xml':
33         root = ET.fromstring(raw)
34         assert root.tag == 'reversegeocode'
35     else:
36         result = json.loads(raw)
37         assert isinstance(result, dict)
38
39
40 @pytest.mark.parametrize('fmt', FORMATS)
41 def test_format_reverse_no_result(fmt):
42     raw = v1_format.format_result(napi.ReverseResults(), fmt, {})
43
44     if fmt == 'xml':
45         root = ET.fromstring(raw)
46         assert root.find('error').text == 'Unable to geocode'
47     else:
48         assert json.loads(raw) == {'error': 'Unable to geocode'}
49
50
51 @pytest.mark.parametrize('fmt', FORMATS)
52 def test_format_reverse_with_osm_id(fmt):
53     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
54                                  ('amenity', 'post_box'),
55                                  napi.Point(0.3, -8.9),
56                                  place_id=5564,
57                                  osm_object=('N', 23))
58
59     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {})
60
61     if fmt == 'xml':
62         root = ET.fromstring(raw).find('result')
63         assert root.attrib['osm_type'] == 'node'
64         assert root.attrib['osm_id'] == '23'
65     else:
66         result = json.loads(raw)
67         if fmt == 'geocodejson':
68             props = result['features'][0]['properties']['geocoding']
69         elif fmt == 'geojson':
70             props = result['features'][0]['properties']
71         else:
72             props = result
73         assert props['osm_type'] == 'node'
74         assert props['osm_id'] == 23
75
76
77 @pytest.mark.parametrize('fmt', FORMATS)
78 def test_format_reverse_with_address(fmt):
79     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
80                                  ('place', 'thing'),
81                                  napi.Point(1.0, 2.0),
82                                  country_code='fe',
83                                  address_rows=napi.AddressLines([
84                                    napi.AddressLine(place_id=None,
85                                                     osm_object=None,
86                                                     category=('place', 'county'),
87                                                     names={'name': 'Hello'},
88                                                     extratags=None,
89                                                     admin_level=5,
90                                                     fromarea=False,
91                                                     isaddress=True,
92                                                     rank_address=10,
93                                                     distance=0.0),
94                                    napi.AddressLine(place_id=None,
95                                                     osm_object=None,
96                                                     category=('place', 'county'),
97                                                     names={'name': 'ByeBye'},
98                                                     extratags=None,
99                                                     admin_level=5,
100                                                     fromarea=False,
101                                                     isaddress=False,
102                                                     rank_address=10,
103                                                     distance=0.0)
104                                  ]))
105     napi.Locales().localize_results([reverse])
106
107     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
108                                   {'addressdetails': True})
109     if fmt == 'xml':
110         root = ET.fromstring(raw)
111         assert root.find('addressparts').find('county').text == 'Hello'
112     else:
113         result = json.loads(raw)
114         assert isinstance(result, dict)
115
116         if fmt == 'geocodejson':
117             props = result['features'][0]['properties']['geocoding']
118             assert 'admin' in props
119             assert props['county'] == 'Hello'
120         else:
121             if fmt == 'geojson':
122                 props = result['features'][0]['properties']
123             else:
124                 props = result
125             assert 'address' in props
126
127
128 def test_format_reverse_geocodejson_special_parts():
129     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
130                                  ('place', 'house'),
131                                  napi.Point(1.0, 2.0),
132                                  place_id=33,
133                                  country_code='fe',
134                                  address_rows=napi.AddressLines([
135                                    napi.AddressLine(place_id=None,
136                                                     osm_object=None,
137                                                     category=('place', 'house_number'),
138                                                     names={'ref': '1'},
139                                                     extratags=None,
140                                                     admin_level=15,
141                                                     fromarea=False,
142                                                     isaddress=True,
143                                                     rank_address=10,
144                                                     distance=0.0),
145                                    napi.AddressLine(place_id=None,
146                                                     osm_object=None,
147                                                     category=('place', 'postcode'),
148                                                     names={'ref': '99446'},
149                                                     extratags=None,
150                                                     admin_level=11,
151                                                     fromarea=False,
152                                                     isaddress=True,
153                                                     rank_address=10,
154                                                     distance=0.0),
155                                    napi.AddressLine(place_id=33,
156                                                     osm_object=None,
157                                                     category=('place', 'county'),
158                                                     names={'name': 'Hello'},
159                                                     extratags=None,
160                                                     admin_level=5,
161                                                     fromarea=False,
162                                                     isaddress=True,
163                                                     rank_address=10,
164                                                     distance=0.0)
165                                  ]))
166
167     napi.Locales().localize_results([reverse])
168
169     raw = v1_format.format_result(napi.ReverseResults([reverse]), 'geocodejson',
170                                   {'addressdetails': True})
171
172     props = json.loads(raw)['features'][0]['properties']['geocoding']
173     assert props['housenumber'] == '1'
174     assert props['postcode'] == '99446'
175     assert 'county' not in props
176
177
178 @pytest.mark.parametrize('fmt', FORMATS)
179 def test_format_reverse_with_address_none(fmt):
180     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
181                                  ('place', 'thing'),
182                                  napi.Point(1.0, 2.0),
183                                  address_rows=napi.AddressLines())
184
185     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
186                                   {'addressdetails': True})
187
188     if fmt == 'xml':
189         root = ET.fromstring(raw)
190         assert root.find('addressparts') is None
191     else:
192         result = json.loads(raw)
193         assert isinstance(result, dict)
194
195         if fmt == 'geocodejson':
196             props = result['features'][0]['properties']['geocoding']
197             print(props)
198             assert 'admin' in props
199         else:
200             if fmt == 'geojson':
201                 props = result['features'][0]['properties']
202             else:
203                 props = result
204             assert 'address' in props
205
206
207 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
208 def test_format_reverse_with_extratags(fmt):
209     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
210                                  ('place', 'thing'),
211                                  napi.Point(1.0, 2.0),
212                                  extratags={'one': 'A', 'two': 'B'})
213
214     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
215                                   {'extratags': True})
216
217     if fmt == 'xml':
218         root = ET.fromstring(raw)
219         assert root.find('extratags').find('tag').attrib['key'] == 'one'
220     else:
221         result = json.loads(raw)
222         if fmt == 'geojson':
223             extra = result['features'][0]['properties']['extratags']
224         else:
225             extra = result['extratags']
226
227         assert extra == {'one': 'A', 'two': 'B'}
228
229
230 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
231 def test_format_reverse_with_extratags_none(fmt):
232     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
233                                  ('place', 'thing'),
234                                  napi.Point(1.0, 2.0))
235
236     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
237                                   {'extratags': True})
238
239     if fmt == 'xml':
240         root = ET.fromstring(raw)
241         assert root.find('extratags') is not None
242     else:
243         result = json.loads(raw)
244         if fmt == 'geojson':
245             extra = result['features'][0]['properties']['extratags']
246         else:
247             extra = result['extratags']
248
249         assert extra is None
250
251
252 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
253 def test_format_reverse_with_namedetails_with_name(fmt):
254     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
255                                  ('place', 'thing'),
256                                  napi.Point(1.0, 2.0),
257                                  names={'name': 'A', 'ref': '1'})
258
259     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
260                                   {'namedetails': True})
261
262     if fmt == 'xml':
263         root = ET.fromstring(raw)
264         assert root.find('namedetails').find('name').text == 'A'
265     else:
266         result = json.loads(raw)
267         if fmt == 'geojson':
268             extra = result['features'][0]['properties']['namedetails']
269         else:
270             extra = result['namedetails']
271
272         assert extra == {'name': 'A', 'ref': '1'}
273
274
275 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
276 def test_format_reverse_with_namedetails_without_name(fmt):
277     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
278                                  ('place', 'thing'),
279                                  napi.Point(1.0, 2.0))
280
281     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
282                                   {'namedetails': True})
283
284     if fmt == 'xml':
285         root = ET.fromstring(raw)
286         assert root.find('namedetails') is not None
287     else:
288         result = json.loads(raw)
289         if fmt == 'geojson':
290             extra = result['features'][0]['properties']['namedetails']
291         else:
292             extra = result['namedetails']
293
294         assert extra is None
295
296
297 @pytest.mark.parametrize('fmt', ['json', 'jsonv2'])
298 def test_search_details_with_icon_available(fmt):
299     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
300                                  ('amenity', 'restaurant'),
301                                  napi.Point(1.0, 2.0))
302
303     result = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
304                                      {'icon_base_url': 'foo'})
305
306     js = json.loads(result)
307
308     assert js['icon'] == 'foo/food_restaurant.p.20.png'
309
310
311 @pytest.mark.parametrize('fmt', ['json', 'jsonv2'])
312 def test_search_details_with_icon_not_available(fmt):
313     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
314                                  ('amenity', 'tree'),
315                                  napi.Point(1.0, 2.0))
316
317     result = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
318                                      {'icon_base_url': 'foo'})
319
320     assert 'icon' not in json.loads(result)