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 results for the V1 API.
10 These test only ensure that the Python code is correct.
11 For functional tests see BDD test suite.
18 import nominatim.api.v1 as api_impl
19 import nominatim.api as napi
20 from nominatim.version import NOMINATIM_VERSION
22 STATUS_FORMATS = {'text', 'json'}
26 def test_status_format_list():
27 assert set(api_impl.list_formats(napi.StatusResult)) == STATUS_FORMATS
30 @pytest.mark.parametrize('fmt', list(STATUS_FORMATS))
31 def test_status_supported(fmt):
32 assert api_impl.supports_format(napi.StatusResult, fmt)
35 def test_status_unsupported():
36 assert not api_impl.supports_format(napi.StatusResult, 'gagaga')
39 def test_status_format_text():
40 assert api_impl.format_result(napi.StatusResult(0, 'message here'), 'text', {}) == 'OK'
43 def test_status_format_text():
44 assert api_impl.format_result(napi.StatusResult(500, 'message here'), 'text', {}) == 'ERROR: message here'
47 def test_status_format_json_minimal():
48 status = napi.StatusResult(700, 'Bad format.')
50 result = api_impl.format_result(status, 'json', {})
52 assert result == '{"status":700,"message":"Bad format.","software_version":"%s"}' % (NOMINATIM_VERSION, )
55 def test_status_format_json_full():
56 status = napi.StatusResult(0, 'OK')
57 status.data_updated = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
58 status.database_version = '5.6'
60 result = api_impl.format_result(status, 'json', {})
62 assert result == '{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"%s","database_version":"5.6"}' % (NOMINATIM_VERSION, )
67 def test_search_details_minimal():
68 search = napi.DetailedResult(napi.SourceTable.PLACEX,
72 result = api_impl.format_result(search, 'json', {})
74 assert json.loads(result) == \
80 'calculated_importance': pytest.approx(0.00001),
86 'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]},
87 'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]},
91 def test_search_details_full():
92 import_date = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
93 search = napi.DetailedResult(
94 source_table=napi.SourceTable.PLACEX,
95 category=('amenity', 'bank'),
96 centroid=napi.Point(56.947, -87.44),
99 linked_place_id=55693,
100 osm_object=('W', 442100),
102 names={'name': 'Bank', 'name:fr': 'Banque'},
103 address={'city': 'Niento', 'housenumber': ' 3'},
104 extratags={'atm': 'yes'},
112 indexed_date = import_date
114 search.localize(napi.Locales())
116 result = api_impl.format_result(search, 'json', {})
118 assert json.loads(result) == \
120 'parent_place_id': 114,
123 'category': 'amenity',
127 'names': {'name': 'Bank', 'name:fr': 'Banque'},
128 'addresstags': {'city': 'Niento', 'housenumber': ' 3'},
130 'calculated_postcode': '556 X23',
131 'country_code': 'll',
132 'indexed_date': '2010-02-07T20:20:03+00:00',
133 'importance': pytest.approx(0.0443),
134 'calculated_importance': pytest.approx(0.0443),
135 'extratags': {'atm': 'yes'},
136 'calculated_wikipedia': 'en:Bank',
140 'centroid': {'type': 'Point', 'coordinates': [56.947, -87.44]},
141 'geometry': {'type': 'Point', 'coordinates': [56.947, -87.44]},
145 @pytest.mark.parametrize('gtype,isarea', [('ST_Point', False),
146 ('ST_LineString', False),
147 ('ST_Polygon', True),
148 ('ST_MultiPolygon', True)])
149 def test_search_details_no_geometry(gtype, isarea):
150 search = napi.DetailedResult(napi.SourceTable.PLACEX,
152 napi.Point(1.0, 2.0),
153 geometry={'type': gtype})
155 result = api_impl.format_result(search, 'json', {})
156 js = json.loads(result)
158 assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
159 assert js['isarea'] == isarea
162 def test_search_details_with_geometry():
163 search = napi.DetailedResult(napi.SourceTable.PLACEX,
165 napi.Point(1.0, 2.0),
166 geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
168 result = api_impl.format_result(search, 'json', {})
169 js = json.loads(result)
171 assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
172 assert js['isarea'] == False
175 def test_search_details_with_icon_available():
176 search = napi.DetailedResult(napi.SourceTable.PLACEX,
177 ('amenity', 'restaurant'),
178 napi.Point(1.0, 2.0))
180 result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'})
181 js = json.loads(result)
183 assert js['icon'] == 'foo/food_restaurant.p.20.png'
186 def test_search_details_with_icon_not_available():
187 search = napi.DetailedResult(napi.SourceTable.PLACEX,
189 napi.Point(1.0, 2.0))
191 result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'})
192 js = json.loads(result)
194 assert 'icon' not in js
197 def test_search_details_with_address_minimal():
198 search = napi.DetailedResult(napi.SourceTable.PLACEX,
200 napi.Point(1.0, 2.0),
202 napi.AddressLine(place_id=None,
204 category=('bnd', 'note'),
214 result = api_impl.format_result(search, 'json', {})
215 js = json.loads(result)
217 assert js['address'] == [{'localname': '',
225 @pytest.mark.parametrize('field,outfield', [('address_rows', 'address'),
226 ('linked_rows', 'linked_places'),
227 ('parented_rows', 'hierarchy')
229 def test_search_details_with_further_infos(field, outfield):
230 search = napi.DetailedResult(napi.SourceTable.PLACEX,
232 napi.Point(1.0, 2.0))
234 setattr(search, field, [napi.AddressLine(place_id=3498,
235 osm_object=('R', 442),
236 category=('bnd', 'note'),
237 names={'name': 'Trespass'},
238 extratags={'access': 'no',
239 'place_type': 'spec'},
247 result = api_impl.format_result(search, 'json', {})
248 js = json.loads(result)
250 assert js[outfield] == [{'localname': 'Trespass',
254 'place_type': 'spec',
263 def test_search_details_grouped_hierarchy():
264 search = napi.DetailedResult(napi.SourceTable.PLACEX,
266 napi.Point(1.0, 2.0),
268 [napi.AddressLine(place_id=3498,
269 osm_object=('R', 442),
270 category=('bnd', 'note'),
271 names={'name': 'Trespass'},
272 extratags={'access': 'no',
273 'place_type': 'spec'},
281 result = api_impl.format_result(search, 'json', {'group_hierarchy': True})
282 js = json.loads(result)
284 assert js['hierarchy'] == {'note': [{'localname': 'Trespass',
288 'place_type': 'spec',
297 def test_search_details_keywords_name():
298 search = napi.DetailedResult(napi.SourceTable.PLACEX,
300 napi.Point(1.0, 2.0),
302 napi.WordInfo(23, 'foo', 'mefoo'),
303 napi.WordInfo(24, 'foo', 'bafoo')])
305 result = api_impl.format_result(search, 'json', {'keywords': True})
306 js = json.loads(result)
308 assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'},
309 {'id': 24, 'token': 'foo'}],
313 def test_search_details_keywords_address():
314 search = napi.DetailedResult(napi.SourceTable.PLACEX,
316 napi.Point(1.0, 2.0),
318 napi.WordInfo(23, 'foo', 'mefoo'),
319 napi.WordInfo(24, 'foo', 'bafoo')])
321 result = api_impl.format_result(search, 'json', {'keywords': True})
322 js = json.loads(result)
324 assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'},
325 {'id': 24, 'token': 'foo'}],