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 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 from nominatim_api.v1.format import dispatch as v1_format
19 import nominatim_api as napi
21 STATUS_FORMATS = {'text', 'json'}
25 def test_status_format_list():
26 assert set(v1_format.list_formats(napi.StatusResult)) == STATUS_FORMATS
29 @pytest.mark.parametrize('fmt', list(STATUS_FORMATS))
30 def test_status_supported(fmt):
31 assert v1_format.supports_format(napi.StatusResult, fmt)
34 def test_status_unsupported():
35 assert not v1_format.supports_format(napi.StatusResult, 'gagaga')
38 def test_status_format_text():
39 assert v1_format.format_result(napi.StatusResult(0, 'message here'), 'text', {}) == 'OK'
42 def test_status_format_text():
43 assert v1_format.format_result(napi.StatusResult(500, 'message here'), 'text', {}) == 'ERROR: message here'
46 def test_status_format_json_minimal():
47 status = napi.StatusResult(700, 'Bad format.')
49 result = v1_format.format_result(status, 'json', {})
52 f'{{"status":700,"message":"Bad format.","software_version":"{napi.__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 = v1_format.format_result(status, 'json', {})
63 f'{{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"{napi.__version__}","database_version":"5.6"}}'
68 def test_search_details_minimal():
69 search = napi.DetailedResult(napi.SourceTable.PLACEX,
73 result = v1_format.format_result(search, 'json', {})
75 assert json.loads(result) == \
81 'calculated_importance': pytest.approx(0.00001),
87 'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]},
88 'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]},
92 def test_search_details_full():
93 import_date = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
94 search = napi.DetailedResult(
95 source_table=napi.SourceTable.PLACEX,
96 category=('amenity', 'bank'),
97 centroid=napi.Point(56.947, -87.44),
100 linked_place_id=55693,
101 osm_object=('W', 442100),
103 names={'name': 'Bank', 'name:fr': 'Banque'},
104 address={'city': 'Niento', 'housenumber': ' 3'},
105 extratags={'atm': 'yes'},
113 indexed_date = import_date
115 search.localize(napi.Locales())
117 result = v1_format.format_result(search, 'json', {})
119 assert json.loads(result) == \
121 'parent_place_id': 114,
124 'category': 'amenity',
128 'names': {'name': 'Bank', 'name:fr': 'Banque'},
129 'addresstags': {'city': 'Niento', 'housenumber': ' 3'},
131 'calculated_postcode': '556 X23',
132 'country_code': 'll',
133 'indexed_date': '2010-02-07T20:20:03+00:00',
134 'importance': pytest.approx(0.0443),
135 'calculated_importance': pytest.approx(0.0443),
136 'extratags': {'atm': 'yes'},
137 'calculated_wikipedia': 'en:Bank',
141 'centroid': {'type': 'Point', 'coordinates': [56.947, -87.44]},
142 'geometry': {'type': 'Point', 'coordinates': [56.947, -87.44]},
146 @pytest.mark.parametrize('gtype,isarea', [('ST_Point', False),
147 ('ST_LineString', False),
148 ('ST_Polygon', True),
149 ('ST_MultiPolygon', True)])
150 def test_search_details_no_geometry(gtype, isarea):
151 search = napi.DetailedResult(napi.SourceTable.PLACEX,
153 napi.Point(1.0, 2.0),
154 geometry={'type': gtype})
156 result = v1_format.format_result(search, 'json', {})
157 js = json.loads(result)
159 assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
160 assert js['isarea'] == isarea
163 def test_search_details_with_geometry():
164 search = napi.DetailedResult(napi.SourceTable.PLACEX,
166 napi.Point(1.0, 2.0),
167 geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
169 result = v1_format.format_result(search, 'json', {})
170 js = json.loads(result)
172 assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
173 assert js['isarea'] == False
176 def test_search_details_with_icon_available():
177 search = napi.DetailedResult(napi.SourceTable.PLACEX,
178 ('amenity', 'restaurant'),
179 napi.Point(1.0, 2.0))
181 result = v1_format.format_result(search, 'json', {'icon_base_url': 'foo'})
182 js = json.loads(result)
184 assert js['icon'] == 'foo/food_restaurant.p.20.png'
187 def test_search_details_with_icon_not_available():
188 search = napi.DetailedResult(napi.SourceTable.PLACEX,
190 napi.Point(1.0, 2.0))
192 result = v1_format.format_result(search, 'json', {'icon_base_url': 'foo'})
193 js = json.loads(result)
195 assert 'icon' not in js
198 def test_search_details_with_address_minimal():
199 search = napi.DetailedResult(napi.SourceTable.PLACEX,
201 napi.Point(1.0, 2.0),
203 napi.AddressLine(place_id=None,
205 category=('bnd', 'note'),
215 result = v1_format.format_result(search, 'json', {})
216 js = json.loads(result)
218 assert js['address'] == [{'localname': '',
226 @pytest.mark.parametrize('field,outfield', [('address_rows', 'address'),
227 ('linked_rows', 'linked_places'),
228 ('parented_rows', 'hierarchy')
230 def test_search_details_with_further_infos(field, outfield):
231 search = napi.DetailedResult(napi.SourceTable.PLACEX,
233 napi.Point(1.0, 2.0))
235 setattr(search, field, [napi.AddressLine(place_id=3498,
236 osm_object=('R', 442),
237 category=('bnd', 'note'),
238 names={'name': 'Trespass'},
239 extratags={'access': 'no',
240 'place_type': 'spec'},
248 result = v1_format.format_result(search, 'json', {})
249 js = json.loads(result)
251 assert js[outfield] == [{'localname': 'Trespass',
255 'place_type': 'spec',
264 def test_search_details_grouped_hierarchy():
265 search = napi.DetailedResult(napi.SourceTable.PLACEX,
267 napi.Point(1.0, 2.0),
269 [napi.AddressLine(place_id=3498,
270 osm_object=('R', 442),
271 category=('bnd', 'note'),
272 names={'name': 'Trespass'},
273 extratags={'access': 'no',
274 'place_type': 'spec'},
282 result = v1_format.format_result(search, 'json', {'group_hierarchy': True})
283 js = json.loads(result)
285 assert js['hierarchy'] == {'note': [{'localname': 'Trespass',
289 'place_type': 'spec',
298 def test_search_details_keywords_name():
299 search = napi.DetailedResult(napi.SourceTable.PLACEX,
301 napi.Point(1.0, 2.0),
303 napi.WordInfo(23, 'foo', 'mefoo'),
304 napi.WordInfo(24, 'foo', 'bafoo')])
306 result = v1_format.format_result(search, 'json', {'keywords': True})
307 js = json.loads(result)
309 assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'},
310 {'id': 24, 'token': 'foo'}],
314 def test_search_details_keywords_address():
315 search = napi.DetailedResult(napi.SourceTable.PLACEX,
317 napi.Point(1.0, 2.0),
319 napi.WordInfo(23, 'foo', 'mefoo'),
320 napi.WordInfo(24, 'foo', 'bafoo')])
322 result = v1_format.format_result(search, 'json', {'keywords': True})
323 js = json.loads(result)
325 assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'},
326 {'id': 24, 'token': 'foo'}],