1 # SPDX-License-Identifier: GPL-2.0-only
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2023 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 import nominatim.api.v1 as api_impl
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 = api_impl.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 = api_impl.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 = api_impl.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'},
105 raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
106 {'addressdetails': True})
110 root = ET.fromstring(raw)
111 assert root.find('addressparts').find('county').text == 'Hello'
113 result = json.loads(raw)
114 assert isinstance(result, dict)
116 if fmt == 'geocodejson':
117 props = result['features'][0]['properties']['geocoding']
118 assert 'admin' in props
119 assert props['county'] == 'Hello'
122 props = result['features'][0]['properties']
125 assert 'address' in props
128 def test_format_reverse_geocodejson_special_parts():
129 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
131 napi.Point(1.0, 2.0),
134 address_rows=napi.AddressLines([
135 napi.AddressLine(place_id=None,
137 category=('place', 'house_number'),
145 napi.AddressLine(place_id=None,
147 category=('place', 'postcode'),
148 names={'ref': '99446'},
155 napi.AddressLine(place_id=33,
157 category=('place', 'county'),
158 names={'name': 'Hello'},
167 raw = api_impl.format_result(napi.ReverseResults([reverse]), 'geocodejson',
168 {'addressdetails': True})
170 props = json.loads(raw)['features'][0]['properties']['geocoding']
171 assert props['housenumber'] == '1'
172 assert props['postcode'] == '99446'
173 assert 'county' not in props
176 @pytest.mark.parametrize('fmt', FORMATS)
177 def test_format_reverse_with_address_none(fmt):
178 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
180 napi.Point(1.0, 2.0),
181 address_rows=napi.AddressLines())
183 raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
184 {'addressdetails': True})
188 root = ET.fromstring(raw)
189 assert root.find('addressparts') is None
191 result = json.loads(raw)
192 assert isinstance(result, dict)
194 if fmt == 'geocodejson':
195 props = result['features'][0]['properties']['geocoding']
197 assert 'admin' in props
200 props = result['features'][0]['properties']
203 assert 'address' in props
206 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
207 def test_format_reverse_with_extratags(fmt):
208 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
210 napi.Point(1.0, 2.0),
211 extratags={'one': 'A', 'two':'B'})
213 raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
217 root = ET.fromstring(raw)
218 assert root.find('extratags').find('tag').attrib['key'] == 'one'
220 result = json.loads(raw)
222 extra = result['features'][0]['properties']['extratags']
224 extra = result['extratags']
226 assert extra == {'one': 'A', 'two':'B'}
229 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
230 def test_format_reverse_with_extratags_none(fmt):
231 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
233 napi.Point(1.0, 2.0))
235 raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
239 root = ET.fromstring(raw)
240 assert root.find('extratags') is not None
242 result = json.loads(raw)
244 extra = result['features'][0]['properties']['extratags']
246 extra = result['extratags']
251 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
252 def test_format_reverse_with_namedetails_with_name(fmt):
253 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
255 napi.Point(1.0, 2.0),
256 names={'name': 'A', 'ref':'1'})
258 raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
259 {'namedetails': True})
262 root = ET.fromstring(raw)
263 assert root.find('namedetails').find('name').text == 'A'
265 result = json.loads(raw)
267 extra = result['features'][0]['properties']['namedetails']
269 extra = result['namedetails']
271 assert extra == {'name': 'A', 'ref':'1'}
274 @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml'])
275 def test_format_reverse_with_namedetails_without_name(fmt):
276 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
278 napi.Point(1.0, 2.0))
280 raw = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
281 {'namedetails': True})
284 root = ET.fromstring(raw)
285 assert root.find('namedetails') is not None
287 result = json.loads(raw)
289 extra = result['features'][0]['properties']['namedetails']
291 extra = result['namedetails']
296 @pytest.mark.parametrize('fmt', ['json', 'jsonv2'])
297 def test_search_details_with_icon_available(fmt):
298 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
299 ('amenity', 'restaurant'),
300 napi.Point(1.0, 2.0))
302 result = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
303 {'icon_base_url': 'foo'})
305 js = json.loads(result)
307 assert js['icon'] == 'foo/food_restaurant.p.20.png'
310 @pytest.mark.parametrize('fmt', ['json', 'jsonv2'])
311 def test_search_details_with_icon_not_available(fmt):
312 reverse = napi.ReverseResult(napi.SourceTable.PLACEX,
314 napi.Point(1.0, 2.0))
316 result = api_impl.format_result(napi.ReverseResults([reverse]), fmt,
317 {'icon_base_url': 'foo'})
319 assert 'icon' not in json.loads(result)