1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Tests for formatting reverse results for the V1 API.
10 These test only ensure that the Python code is correct.
11 For functional tests see BDD test suite.
14 import xml.etree.ElementTree as ET
18 from nominatim_api.v1.format import dispatch as v1_format
19 import nominatim_api as napi
21 FORMATS = ['json', 'jsonv2', 'geojson', 'geocodejson', 'xml']
23 @pytest.mark.parametrize('fmt', FORMATS)
24 def test_format_reverse_minimal(fmt):
25 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
26 ('amenity', 'post_box'),
27 napi.Point(0.3, -8.9))
29 raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {})
32 root = ET.fromstring(raw)
33 assert root.tag == 'reversegeocode'
35 result = json.loads(raw)
36 assert isinstance(result, dict)
39 @pytest.mark.parametrize('fmt', FORMATS)
40 def test_format_reverse_no_result(fmt):
41 raw = v1_format.format_result(napi.ReverseResults(), fmt, {})
44 root = ET.fromstring(raw)
45 assert root.find('error').text == 'Unable to geocode'
47 assert json.loads(raw) == {'error': 'Unable to geocode'}
50 @pytest.mark.parametrize('fmt', FORMATS)
51 def test_format_reverse_with_osm_id(fmt):
52 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
53 ('amenity', 'post_box'),
54 napi.Point(0.3, -8.9),
58 raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, {})
61 root = ET.fromstring(raw).find('result')
62 assert root.attrib['osm_type'] == 'node'
63 assert root.attrib['osm_id'] == '23'
65 result = json.loads(raw)
66 if fmt == 'geocodejson':
67 props = result['features'][0]['properties']['geocoding']
68 elif fmt == 'geojson':
69 props = result['features'][0]['properties']
72 assert props['osm_type'] == 'node'
73 assert props['osm_id'] == 23
76 @pytest.mark.parametrize('fmt', FORMATS)
77 def test_format_reverse_with_address(fmt):
78 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
82 address_rows=napi.AddressLines([
83 napi.AddressLine(place_id=None,
85 category=('place', 'county'),
86 names={'name': 'Hello'},
93 napi.AddressLine(place_id=None,
95 category=('place', 'county'),
96 names={'name': 'ByeBye'},
104 reverse.localize(napi.Locales())
106 raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
107 {'addressdetails': True})
111 root = ET.fromstring(raw)
112 assert root.find('addressparts').find('county').text == 'Hello'
114 result = json.loads(raw)
115 assert isinstance(result, dict)
117 if fmt == 'geocodejson':
118 props = result['features'][0]['properties']['geocoding']
119 assert 'admin' in props
120 assert props['county'] == 'Hello'
123 props = result['features'][0]['properties']
126 assert 'address' in props
129 def test_format_reverse_geocodejson_special_parts():
130 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
132 napi.Point(1.0, 2.0),
135 address_rows=napi.AddressLines([
136 napi.AddressLine(place_id=None,
138 category=('place', 'house_number'),
146 napi.AddressLine(place_id=None,
148 category=('place', 'postcode'),
149 names={'ref': '99446'},
156 napi.AddressLine(place_id=33,
158 category=('place', 'county'),
159 names={'name': 'Hello'},
168 reverse.localize(napi.Locales())
170 raw = v1_format.format_result(napi.ReverseResults([reverse]), 'geocodejson',
171 {'addressdetails': True})
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
179 @pytest.mark.parametrize('fmt', FORMATS)
180 def test_format_reverse_with_address_none(fmt):
181 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
183 napi.Point(1.0, 2.0),
184 address_rows=napi.AddressLines())
186 raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
187 {'addressdetails': True})
191 root = ET.fromstring(raw)
192 assert root.find('addressparts') is None
194 result = json.loads(raw)
195 assert isinstance(result, dict)
197 if fmt == 'geocodejson':
198 props = result['features'][0]['properties']['geocoding']
200 assert 'admin' in props
203 props = result['features'][0]['properties']
206 assert 'address' in props
209 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
210 def test_format_reverse_with_extratags(fmt):
211 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
213 napi.Point(1.0, 2.0),
214 extratags={'one': 'A', 'two':'B'})
216 raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
220 root = ET.fromstring(raw)
221 assert root.find('extratags').find('tag').attrib['key'] == 'one'
223 result = json.loads(raw)
225 extra = result['features'][0]['properties']['extratags']
227 extra = result['extratags']
229 assert extra == {'one': 'A', 'two':'B'}
232 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
233 def test_format_reverse_with_extratags_none(fmt):
234 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
236 napi.Point(1.0, 2.0))
238 raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
242 root = ET.fromstring(raw)
243 assert root.find('extratags') is not None
245 result = json.loads(raw)
247 extra = result['features'][0]['properties']['extratags']
249 extra = result['extratags']
254 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
255 def test_format_reverse_with_namedetails_with_name(fmt):
256 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
258 napi.Point(1.0, 2.0),
259 names={'name': 'A', 'ref':'1'})
261 raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
262 {'namedetails': True})
265 root = ET.fromstring(raw)
266 assert root.find('namedetails').find('name').text == 'A'
268 result = json.loads(raw)
270 extra = result['features'][0]['properties']['namedetails']
272 extra = result['namedetails']
274 assert extra == {'name': 'A', 'ref':'1'}
277 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
278 def test_format_reverse_with_namedetails_without_name(fmt):
279 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
281 napi.Point(1.0, 2.0))
283 raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
284 {'namedetails': True})
287 root = ET.fromstring(raw)
288 assert root.find('namedetails') is not None
290 result = json.loads(raw)
292 extra = result['features'][0]['properties']['namedetails']
294 extra = result['namedetails']
299 @pytest.mark.parametrize('fmt', ['json', 'jsonv2'])
300 def test_search_details_with_icon_available(fmt):
301 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
302 ('amenity', 'restaurant'),
303 napi.Point(1.0, 2.0))
305 result = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
306 {'icon_base_url': 'foo'})
308 js = json.loads(result)
310 assert js['icon'] == 'foo/food_restaurant.p.20.png'
313 @pytest.mark.parametrize('fmt', ['json', 'jsonv2'])
314 def test_search_details_with_icon_not_available(fmt):
315 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
317 napi.Point(1.0, 2.0))
319 result = v1_format.format_result(napi.ReverseResults([reverse]), fmt,
320 {'icon_base_url': 'foo'})
322 assert 'icon' not in json.loads(result)