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) == \
79 'calculated_importance': pytest.approx(0.0000001),
83 'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]},
84 'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]},
88 def test_search_details_full():
89 import_date = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
90 search = napi.DetailedResult(
91 source_table=napi.SourceTable.PLACEX,
92 category=('amenity', 'bank'),
93 centroid=napi.Point(56.947, -87.44),
96 linked_place_id=55693,
97 osm_object=('W', 442100),
99 names={'name': 'Bank', 'name:fr': 'Banque'},
100 address={'city': 'Niento', 'housenumber': ' 3'},
101 extratags={'atm': 'yes'},
109 indexed_date = import_date
112 result = api_impl.format_result(search, 'json', {})
114 assert json.loads(result) == \
116 'parent_place_id': 114,
119 'category': 'amenity',
123 'names': {'name': 'Bank', 'name:fr': 'Banque'},
124 'addresstags': {'city': 'Niento', 'housenumber': ' 3'},
126 'calculated_postcode': '556 X23',
127 'country_code': 'll',
128 'indexed_date': '2010-02-07T20:20:03+00:00',
129 'importance': pytest.approx(0.0443),
130 'calculated_importance': pytest.approx(0.0443),
131 'extratags': {'atm': 'yes'},
132 'calculated_wikipedia': 'en:Bank',
136 'centroid': {'type': 'Point', 'coordinates': [56.947, -87.44]},
137 'geometry': {'type': 'Point', 'coordinates': [56.947, -87.44]},
141 @pytest.mark.parametrize('gtype,isarea', [('ST_Point', False),
142 ('ST_LineString', False),
143 ('ST_Polygon', True),
144 ('ST_MultiPolygon', True)])
145 def test_search_details_no_geometry(gtype, isarea):
146 search = napi.DetailedResult(napi.SourceTable.PLACEX,
148 napi.Point(1.0, 2.0),
149 geometry={'type': gtype})
151 result = api_impl.format_result(search, 'json', {})
152 js = json.loads(result)
154 assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
155 assert js['isarea'] == isarea
158 def test_search_details_with_geometry():
159 search = napi.DetailedResult(napi.SourceTable.PLACEX,
161 napi.Point(1.0, 2.0),
162 geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
164 result = api_impl.format_result(search, 'json', {})
165 js = json.loads(result)
167 assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
168 assert js['isarea'] == False
171 def test_search_details_with_icon_available():
172 search = napi.DetailedResult(napi.SourceTable.PLACEX,
173 ('amenity', 'restaurant'),
174 napi.Point(1.0, 2.0))
176 result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'})
177 js = json.loads(result)
179 assert js['icon'] == 'foo/food_restaurant.p.20.png'
182 def test_search_details_with_icon_not_available():
183 search = napi.DetailedResult(napi.SourceTable.PLACEX,
185 napi.Point(1.0, 2.0))
187 result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'})
188 js = json.loads(result)
190 assert 'icon' not in js
193 def test_search_details_with_address_minimal():
194 search = napi.DetailedResult(napi.SourceTable.PLACEX,
196 napi.Point(1.0, 2.0),
198 napi.AddressLine(place_id=None,
200 category=('bnd', 'note'),
210 result = api_impl.format_result(search, 'json', {})
211 js = json.loads(result)
213 assert js['address'] == [{'localname': '',
221 @pytest.mark.parametrize('field,outfield', [('address_rows', 'address'),
222 ('linked_rows', 'linked_places'),
223 ('parented_rows', 'hierarchy')
225 def test_search_details_with_further_infos(field, outfield):
226 search = napi.DetailedResult(napi.SourceTable.PLACEX,
228 napi.Point(1.0, 2.0))
230 setattr(search, field, [napi.AddressLine(place_id=3498,
231 osm_object=('R', 442),
232 category=('bnd', 'note'),
233 names={'name': 'Trespass'},
234 extratags={'access': 'no',
235 'place_type': 'spec'},
243 result = api_impl.format_result(search, 'json', {})
244 js = json.loads(result)
246 assert js[outfield] == [{'localname': 'Trespass',
250 'place_type': 'spec',
259 def test_search_details_grouped_hierarchy():
260 search = napi.DetailedResult(napi.SourceTable.PLACEX,
262 napi.Point(1.0, 2.0),
264 [napi.AddressLine(place_id=3498,
265 osm_object=('R', 442),
266 category=('bnd', 'note'),
267 names={'name': 'Trespass'},
268 extratags={'access': 'no',
269 'place_type': 'spec'},
277 result = api_impl.format_result(search, 'json', {'group_hierarchy': True})
278 js = json.loads(result)
280 assert js['hierarchy'] == {'note': [{'localname': 'Trespass',
284 'place_type': 'spec',
293 def test_search_details_keywords_name():
294 search = napi.DetailedResult(napi.SourceTable.PLACEX,
296 napi.Point(1.0, 2.0),
298 napi.WordInfo(23, 'foo', 'mefoo'),
299 napi.WordInfo(24, 'foo', 'bafoo')])
301 result = api_impl.format_result(search, 'json', {'keywords': True})
302 js = json.loads(result)
304 assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'},
305 {'id': 24, 'token': 'foo'}],
309 def test_search_details_keywords_address():
310 search = napi.DetailedResult(napi.SourceTable.PLACEX,
312 napi.Point(1.0, 2.0),
314 napi.WordInfo(23, 'foo', 'mefoo'),
315 napi.WordInfo(24, 'foo', 'bafoo')])
317 result = api_impl.format_result(search, 'json', {'keywords': True})
318 js = json.loads(result)
320 assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'},
321 {'id': 24, 'token': 'foo'}],