]> git.openstreetmap.org Git - nominatim.git/blob - test/python/api/test_result_formatting_v1.py
enable flake for Python tests
[nominatim.git] / test / python / api / test_result_formatting_v1.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2025 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Tests for formatting results for the V1 API.
9
10 These test only ensure that the Python code is correct.
11 For functional tests see BDD test suite.
12 """
13 import datetime as dt
14 import json
15
16 import pytest
17
18 from nominatim_api.v1.format import dispatch as v1_format
19 import nominatim_api as napi
20
21 STATUS_FORMATS = {'text', 'json'}
22
23 # StatusResult
24
25
26 def test_status_format_list():
27     assert set(v1_format.list_formats(napi.StatusResult)) == STATUS_FORMATS
28
29
30 @pytest.mark.parametrize('fmt', list(STATUS_FORMATS))
31 def test_status_supported(fmt):
32     assert v1_format.supports_format(napi.StatusResult, fmt)
33
34
35 def test_status_unsupported():
36     assert not v1_format.supports_format(napi.StatusResult, 'gagaga')
37
38
39 def test_status_format_text():
40     assert v1_format.format_result(napi.StatusResult(0, 'message here'), 'text', {}) \
41         == 'OK'
42
43
44 def test_status_format_error_text():
45     assert v1_format.format_result(napi.StatusResult(500, 'message here'), 'text', {}) \
46         == 'ERROR: message here'
47
48
49 def test_status_format_json_minimal():
50     status = napi.StatusResult(700, 'Bad format.')
51
52     result = v1_format.format_result(status, 'json', {})
53
54     assert json.loads(result) == {'status': 700,
55                                   'message': 'Bad format.',
56                                   'software_version': napi.__version__}
57
58
59 def test_status_format_json_full():
60     status = napi.StatusResult(0, 'OK')
61     status.data_updated = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
62     status.database_version = '5.6'
63
64     result = v1_format.format_result(status, 'json', {})
65
66     assert json.loads(result) == {'status': 0,
67                                   'message': 'OK',
68                                   'data_updated': '2010-02-07T20:20:03+00:00',
69                                   'software_version': napi.__version__,
70                                   'database_version': '5.6'}
71
72
73 # DetailedResult
74
75 def test_search_details_minimal():
76     search = napi.DetailedResult(napi.SourceTable.PLACEX,
77                                  ('place', 'thing'),
78                                  napi.Point(1.0, 2.0))
79
80     result = v1_format.format_result(search, 'json', {})
81
82     assert json.loads(result) == \
83            {'category': 'place',
84             'type': 'thing',
85             'admin_level': 15,
86             'names': {},
87             'localname': '',
88             'calculated_importance': pytest.approx(0.00001),
89             'rank_address': 30,
90             'rank_search': 30,
91             'isarea': False,
92             'addresstags': {},
93             'extratags': {},
94             'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]},
95             'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]},
96             }
97
98
99 def test_search_details_full():
100     import_date = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
101     search = napi.DetailedResult(
102                   source_table=napi.SourceTable.PLACEX,
103                   category=('amenity', 'bank'),
104                   centroid=napi.Point(56.947, -87.44),
105                   place_id=37563,
106                   parent_place_id=114,
107                   linked_place_id=55693,
108                   osm_object=('W', 442100),
109                   admin_level=14,
110                   names={'name': 'Bank', 'name:fr': 'Banque'},
111                   address={'city': 'Niento', 'housenumber': '  3'},
112                   extratags={'atm': 'yes'},
113                   housenumber='3',
114                   postcode='556 X23',
115                   wikipedia='en:Bank',
116                   rank_address=29,
117                   rank_search=28,
118                   importance=0.0443,
119                   country_code='ll',
120                   indexed_date=import_date
121                   )
122     search.localize(napi.Locales())
123
124     result = v1_format.format_result(search, 'json', {})
125
126     assert json.loads(result) == \
127            {'place_id': 37563,
128             'parent_place_id': 114,
129             'osm_type': 'W',
130             'osm_id': 442100,
131             'category': 'amenity',
132             'type': 'bank',
133             'admin_level': 14,
134             'localname': 'Bank',
135             'names': {'name': 'Bank', 'name:fr': 'Banque'},
136             'addresstags': {'city': 'Niento', 'housenumber': '  3'},
137             'housenumber': '3',
138             'calculated_postcode': '556 X23',
139             'country_code': 'll',
140             'indexed_date': '2010-02-07T20:20:03+00:00',
141             'importance': pytest.approx(0.0443),
142             'calculated_importance': pytest.approx(0.0443),
143             'extratags': {'atm': 'yes'},
144             'calculated_wikipedia': 'en:Bank',
145             'rank_address': 29,
146             'rank_search': 28,
147             'isarea': False,
148             'centroid': {'type': 'Point', 'coordinates': [56.947, -87.44]},
149             'geometry': {'type': 'Point', 'coordinates': [56.947, -87.44]},
150             }
151
152
153 @pytest.mark.parametrize('gtype,isarea', [('ST_Point', False),
154                                           ('ST_LineString', False),
155                                           ('ST_Polygon', True),
156                                           ('ST_MultiPolygon', True)])
157 def test_search_details_no_geometry(gtype, isarea):
158     search = napi.DetailedResult(napi.SourceTable.PLACEX,
159                                  ('place', 'thing'),
160                                  napi.Point(1.0, 2.0),
161                                  geometry={'type': gtype})
162
163     result = v1_format.format_result(search, 'json', {})
164     js = json.loads(result)
165
166     assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
167     assert js['isarea'] == isarea
168
169
170 def test_search_details_with_geometry():
171     search = napi.DetailedResult(
172         napi.SourceTable.PLACEX,
173         ('place', 'thing'),
174         napi.Point(1.0, 2.0),
175         geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
176
177     result = v1_format.format_result(search, 'json', {})
178     js = json.loads(result)
179
180     assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
181     assert js['isarea'] is False
182
183
184 def test_search_details_with_icon_available():
185     search = napi.DetailedResult(napi.SourceTable.PLACEX,
186                                  ('amenity', 'restaurant'),
187                                  napi.Point(1.0, 2.0))
188
189     result = v1_format.format_result(search, 'json', {'icon_base_url': 'foo'})
190     js = json.loads(result)
191
192     assert js['icon'] == 'foo/food_restaurant.p.20.png'
193
194
195 def test_search_details_with_icon_not_available():
196     search = napi.DetailedResult(napi.SourceTable.PLACEX,
197                                  ('amenity', 'tree'),
198                                  napi.Point(1.0, 2.0))
199
200     result = v1_format.format_result(search, 'json', {'icon_base_url': 'foo'})
201     js = json.loads(result)
202
203     assert 'icon' not in js
204
205
206 def test_search_details_with_address_minimal():
207     search = napi.DetailedResult(napi.SourceTable.PLACEX,
208                                  ('place', 'thing'),
209                                  napi.Point(1.0, 2.0),
210                                  address_rows=[
211                                    napi.AddressLine(place_id=None,
212                                                     osm_object=None,
213                                                     category=('bnd', 'note'),
214                                                     names={},
215                                                     extratags=None,
216                                                     admin_level=None,
217                                                     fromarea=False,
218                                                     isaddress=False,
219                                                     rank_address=10,
220                                                     distance=0.0)
221                                  ])
222
223     result = v1_format.format_result(search, 'json', {})
224     js = json.loads(result)
225
226     assert js['address'] == [{'localname': '',
227                               'class': 'bnd',
228                               'type': 'note',
229                               'rank_address': 10,
230                               'distance': 0.0,
231                               'isaddress': False}]
232
233
234 @pytest.mark.parametrize('field,outfield', [('address_rows', 'address'),
235                                             ('linked_rows', 'linked_places'),
236                                             ('parented_rows', 'hierarchy')
237                                             ])
238 def test_search_details_with_further_infos(field, outfield):
239     search = napi.DetailedResult(napi.SourceTable.PLACEX,
240                                  ('place', 'thing'),
241                                  napi.Point(1.0, 2.0))
242
243     setattr(search, field, [napi.AddressLine(place_id=3498,
244                                              osm_object=('R', 442),
245                                              category=('bnd', 'note'),
246                                              names={'name': 'Trespass'},
247                                              extratags={'access': 'no',
248                                                         'place_type': 'spec'},
249                                              admin_level=4,
250                                              fromarea=True,
251                                              isaddress=True,
252                                              rank_address=10,
253                                              distance=0.034)
254                             ])
255
256     result = v1_format.format_result(search, 'json', {})
257     js = json.loads(result)
258
259     assert js[outfield] == [{'localname': 'Trespass',
260                              'place_id': 3498,
261                              'osm_id': 442,
262                              'osm_type': 'R',
263                              'place_type': 'spec',
264                              'class': 'bnd',
265                              'type': 'note',
266                              'admin_level': 4,
267                              'rank_address': 10,
268                              'distance': 0.034,
269                              'isaddress': True}]
270
271
272 def test_search_details_grouped_hierarchy():
273     search = napi.DetailedResult(napi.SourceTable.PLACEX,
274                                  ('place', 'thing'),
275                                  napi.Point(1.0, 2.0),
276                                  parented_rows=[napi.AddressLine(
277                                     place_id=3498,
278                                     osm_object=('R', 442),
279                                     category=('bnd', 'note'),
280                                     names={'name': 'Trespass'},
281                                     extratags={'access': 'no',
282                                                'place_type': 'spec'},
283                                     admin_level=4,
284                                     fromarea=True,
285                                     isaddress=True,
286                                     rank_address=10,
287                                     distance=0.034)])
288
289     result = v1_format.format_result(search, 'json', {'group_hierarchy': True})
290     js = json.loads(result)
291
292     assert js['hierarchy'] == {'note': [{'localname': 'Trespass',
293                                          'place_id': 3498,
294                                          'osm_id': 442,
295                                          'osm_type': 'R',
296                                          'place_type': 'spec',
297                                          'class': 'bnd',
298                                          'type': 'note',
299                                          'admin_level': 4,
300                                          'rank_address': 10,
301                                          'distance': 0.034,
302                                          'isaddress': True}]}
303
304
305 def test_search_details_keywords_name():
306     search = napi.DetailedResult(napi.SourceTable.PLACEX,
307                                  ('place', 'thing'),
308                                  napi.Point(1.0, 2.0),
309                                  name_keywords=[
310                                      napi.WordInfo(23, 'foo', 'mefoo'),
311                                      napi.WordInfo(24, 'foo', 'bafoo')])
312
313     result = v1_format.format_result(search, 'json', {'keywords': True})
314     js = json.loads(result)
315
316     assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'},
317                                        {'id': 24, 'token': 'foo'}],
318                               'address': []}
319
320
321 def test_search_details_keywords_address():
322     search = napi.DetailedResult(napi.SourceTable.PLACEX,
323                                  ('place', 'thing'),
324                                  napi.Point(1.0, 2.0),
325                                  address_keywords=[
326                                      napi.WordInfo(23, 'foo', 'mefoo'),
327                                      napi.WordInfo(24, 'foo', 'bafoo')])
328
329     result = v1_format.format_result(search, 'json', {'keywords': True})
330     js = json.loads(result)
331
332     assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'},
333                                           {'id': 24, 'token': 'foo'}],
334                               'name': []}