]> git.openstreetmap.org Git - nominatim.git/blob - test/python/api/test_result_formatting_v1_reverse.py
enable flake for Python tests
[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     reverse.localize(napi.Locales())
106
107     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
108                                   {'addressdetails': True})
109
110     if fmt == 'xml':
111         root = ET.fromstring(raw)
112         assert root.find('addressparts').find('county').text == 'Hello'
113     else:
114         result = json.loads(raw)
115         assert isinstance(result, dict)
116
117         if fmt == 'geocodejson':
118             props = result['features'][0]['properties']['geocoding']
119             assert 'admin' in props
120             assert props['county'] == 'Hello'
121         else:
122             if fmt == 'geojson':
123                 props = result['features'][0]['properties']
124             else:
125                 props = result
126             assert 'address' in props
127
128
129 def test_format_reverse_geocodejson_special_parts():
130     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
131                                  ('place', 'house'),
132                                  napi.Point(1.0, 2.0),
133                                  place_id=33,
134                                  country_code='fe',
135                                  address_rows=napi.AddressLines([
136                                    napi.AddressLine(place_id=None,
137                                                     osm_object=None,
138                                                     category=('place', 'house_number'),
139                                                     names={'ref': '1'},
140                                                     extratags=None,
141                                                     admin_level=15,
142                                                     fromarea=False,
143                                                     isaddress=True,
144                                                     rank_address=10,
145                                                     distance=0.0),
146                                    napi.AddressLine(place_id=None,
147                                                     osm_object=None,
148                                                     category=('place', 'postcode'),
149                                                     names={'ref': '99446'},
150                                                     extratags=None,
151                                                     admin_level=11,
152                                                     fromarea=False,
153                                                     isaddress=True,
154                                                     rank_address=10,
155                                                     distance=0.0),
156                                    napi.AddressLine(place_id=33,
157                                                     osm_object=None,
158                                                     category=('place', 'county'),
159                                                     names={'name': 'Hello'},
160                                                     extratags=None,
161                                                     admin_level=5,
162                                                     fromarea=False,
163                                                     isaddress=True,
164                                                     rank_address=10,
165                                                     distance=0.0)
166                                  ]))
167
168     reverse.localize(napi.Locales())
169
170     raw = v1_format.format_result(napi.ReverseResults([reverse]), 'geocodejson',
171                                   {'addressdetails': True})
172
173     props = json.loads(raw)['features'][0]['properties']['geocoding']
174     assert props['housenumber'] == '1'
175     assert props['postcode'] == '99446'
176     assert 'county' not in props
177
178
179 @pytest.mark.parametrize('fmt', FORMATS)
180 def test_format_reverse_with_address_none(fmt):
181     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
182                                  ('place', 'thing'),
183                                  napi.Point(1.0, 2.0),
184                                  address_rows=napi.AddressLines())
185
186     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
187                                   {'addressdetails': True})
188
189     if fmt == 'xml':
190         root = ET.fromstring(raw)
191         assert root.find('addressparts') is None
192     else:
193         result = json.loads(raw)
194         assert isinstance(result, dict)
195
196         if fmt == 'geocodejson':
197             props = result['features'][0]['properties']['geocoding']
198             print(props)
199             assert 'admin' in props
200         else:
201             if fmt == 'geojson':
202                 props = result['features'][0]['properties']
203             else:
204                 props = result
205             assert 'address' in props
206
207
208 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
209 def test_format_reverse_with_extratags(fmt):
210     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
211                                  ('place', 'thing'),
212                                  napi.Point(1.0, 2.0),
213                                  extratags={'one': 'A', 'two': 'B'})
214
215     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
216                                   {'extratags': True})
217
218     if fmt == 'xml':
219         root = ET.fromstring(raw)
220         assert root.find('extratags').find('tag').attrib['key'] == 'one'
221     else:
222         result = json.loads(raw)
223         if fmt == 'geojson':
224             extra = result['features'][0]['properties']['extratags']
225         else:
226             extra = result['extratags']
227
228         assert extra == {'one': 'A', 'two': 'B'}
229
230
231 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
232 def test_format_reverse_with_extratags_none(fmt):
233     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
234                                  ('place', 'thing'),
235                                  napi.Point(1.0, 2.0))
236
237     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
238                                   {'extratags': True})
239
240     if fmt == 'xml':
241         root = ET.fromstring(raw)
242         assert root.find('extratags') is not None
243     else:
244         result = json.loads(raw)
245         if fmt == 'geojson':
246             extra = result['features'][0]['properties']['extratags']
247         else:
248             extra = result['extratags']
249
250         assert extra is None
251
252
253 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
254 def test_format_reverse_with_namedetails_with_name(fmt):
255     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
256                                  ('place', 'thing'),
257                                  napi.Point(1.0, 2.0),
258                                  names={'name': 'A', 'ref': '1'})
259
260     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
261                                   {'namedetails': True})
262
263     if fmt == 'xml':
264         root = ET.fromstring(raw)
265         assert root.find('namedetails').find('name').text == 'A'
266     else:
267         result = json.loads(raw)
268         if fmt == 'geojson':
269             extra = result['features'][0]['properties']['namedetails']
270         else:
271             extra = result['namedetails']
272
273         assert extra == {'name': 'A', 'ref': '1'}
274
275
276 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
277 def test_format_reverse_with_namedetails_without_name(fmt):
278     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
279                                  ('place', 'thing'),
280                                  napi.Point(1.0, 2.0))
281
282     raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
283                                   {'namedetails': True})
284
285     if fmt == 'xml':
286         root = ET.fromstring(raw)
287         assert root.find('namedetails') is not None
288     else:
289         result = json.loads(raw)
290         if fmt == 'geojson':
291             extra = result['features'][0]['properties']['namedetails']
292         else:
293             extra = result['namedetails']
294
295         assert extra is None
296
297
298 @pytest.mark.parametrize('fmt', ['json', 'jsonv2'])
299 def test_search_details_with_icon_available(fmt):
300     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
301                                  ('amenity', 'restaurant'),
302                                  napi.Point(1.0, 2.0))
303
304     result = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
305                                      {'icon_base_url': 'foo'})
306
307     js = json.loads(result)
308
309     assert js['icon'] == 'foo/food_restaurant.p.20.png'
310
311
312 @pytest.mark.parametrize('fmt', ['json', 'jsonv2'])
313 def test_search_details_with_icon_not_available(fmt):
314     reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
315                                  ('amenity', 'tree'),
316                                  napi.Point(1.0, 2.0))
317
318     result = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
319                                      {'icon_base_url': 'foo'})
320
321     assert 'icon' not in json.loads(result)