]> git.openstreetmap.org Git - nominatim.git/blob - test/python/api/test_result_formatting_v1.py
indexing: precompute row counts
[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) 2024 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 import nominatim_api.v1 as api_impl
19 import nominatim_api as napi
20
21 STATUS_FORMATS = {'text', 'json'}
22
23 # StatusResult
24
25 def test_status_format_list():
26     assert set(api_impl.list_formats(napi.StatusResult)) == STATUS_FORMATS
27
28
29 @pytest.mark.parametrize('fmt', list(STATUS_FORMATS))
30 def test_status_supported(fmt):
31     assert api_impl.supports_format(napi.StatusResult, fmt)
32
33
34 def test_status_unsupported():
35     assert not api_impl.supports_format(napi.StatusResult, 'gagaga')
36
37
38 def test_status_format_text():
39     assert api_impl.format_result(napi.StatusResult(0, 'message here'), 'text', {}) == 'OK'
40
41
42 def test_status_format_text():
43     assert api_impl.format_result(napi.StatusResult(500, 'message here'), 'text', {}) == 'ERROR: message here'
44
45
46 def test_status_format_json_minimal():
47     status = napi.StatusResult(700, 'Bad format.')
48
49     result = api_impl.format_result(status, 'json', {})
50
51     assert result == \
52            f'{{"status":700,"message":"Bad format.","software_version":"{napi.__version__}"}}'
53
54
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'
59
60     result = api_impl.format_result(status, 'json', {})
61
62     assert result == \
63            f'{{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"{napi.__version__}","database_version":"5.6"}}'
64
65
66 # DetailedResult
67
68 def test_search_details_minimal():
69     search = napi.DetailedResult(napi.SourceTable.PLACEX,
70                                  ('place', 'thing'),
71                                  napi.Point(1.0, 2.0))
72
73     result = api_impl.format_result(search, 'json', {})
74
75     assert json.loads(result) == \
76            {'category': 'place',
77             'type': 'thing',
78             'admin_level': 15,
79             'names': {},
80             'localname': '',
81             'calculated_importance': pytest.approx(0.00001),
82             'rank_address': 30,
83             'rank_search': 30,
84             'isarea': False,
85             'addresstags': {},
86             'extratags': {},
87             'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]},
88             'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]},
89            }
90
91
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),
98                   place_id=37563,
99                   parent_place_id=114,
100                   linked_place_id=55693,
101                   osm_object=('W', 442100),
102                   admin_level=14,
103                   names={'name': 'Bank', 'name:fr': 'Banque'},
104                   address={'city': 'Niento', 'housenumber': '  3'},
105                   extratags={'atm': 'yes'},
106                   housenumber='3',
107                   postcode='556 X23',
108                   wikipedia='en:Bank',
109                   rank_address=29,
110                   rank_search=28,
111                   importance=0.0443,
112                   country_code='ll',
113                   indexed_date = import_date
114                   )
115     search.localize(napi.Locales())
116
117     result = api_impl.format_result(search, 'json', {})
118
119     assert json.loads(result) == \
120            {'place_id': 37563,
121             'parent_place_id': 114,
122             'osm_type': 'W',
123             'osm_id': 442100,
124             'category': 'amenity',
125             'type': 'bank',
126             'admin_level': 14,
127             'localname': 'Bank',
128             'names': {'name': 'Bank', 'name:fr': 'Banque'},
129             'addresstags': {'city': 'Niento', 'housenumber': '  3'},
130             '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',
138             'rank_address': 29,
139             'rank_search': 28,
140             'isarea': False,
141             'centroid': {'type': 'Point', 'coordinates': [56.947, -87.44]},
142             'geometry': {'type': 'Point', 'coordinates': [56.947, -87.44]},
143            }
144
145
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,
152                                ('place', 'thing'),
153                                napi.Point(1.0, 2.0),
154                                geometry={'type': gtype})
155
156     result = api_impl.format_result(search, 'json', {})
157     js = json.loads(result)
158
159     assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
160     assert js['isarea'] == isarea
161
162
163 def test_search_details_with_geometry():
164     search = napi.DetailedResult(napi.SourceTable.PLACEX,
165                                  ('place', 'thing'),
166                                  napi.Point(1.0, 2.0),
167                                  geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
168
169     result = api_impl.format_result(search, 'json', {})
170     js = json.loads(result)
171
172     assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
173     assert js['isarea'] == False
174
175
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))
180
181     result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'})
182     js = json.loads(result)
183
184     assert js['icon'] == 'foo/food_restaurant.p.20.png'
185
186
187 def test_search_details_with_icon_not_available():
188     search = napi.DetailedResult(napi.SourceTable.PLACEX,
189                                  ('amenity', 'tree'),
190                                  napi.Point(1.0, 2.0))
191
192     result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'})
193     js = json.loads(result)
194
195     assert 'icon' not in js
196
197
198 def test_search_details_with_address_minimal():
199     search = napi.DetailedResult(napi.SourceTable.PLACEX,
200                                  ('place', 'thing'),
201                                  napi.Point(1.0, 2.0),
202                                  address_rows=[
203                                    napi.AddressLine(place_id=None,
204                                                     osm_object=None,
205                                                     category=('bnd', 'note'),
206                                                     names={},
207                                                     extratags=None,
208                                                     admin_level=None,
209                                                     fromarea=False,
210                                                     isaddress=False,
211                                                     rank_address=10,
212                                                     distance=0.0)
213                                  ])
214
215     result = api_impl.format_result(search, 'json', {})
216     js = json.loads(result)
217
218     assert js['address'] == [{'localname': '',
219                               'class': 'bnd',
220                               'type': 'note',
221                               'rank_address': 10,
222                               'distance': 0.0,
223                               'isaddress': False}]
224
225
226 @pytest.mark.parametrize('field,outfield', [('address_rows', 'address'),
227                                             ('linked_rows', 'linked_places'),
228                                             ('parented_rows', 'hierarchy')
229                                            ])
230 def test_search_details_with_further_infos(field, outfield):
231     search = napi.DetailedResult(napi.SourceTable.PLACEX,
232                                  ('place', 'thing'),
233                                  napi.Point(1.0, 2.0))
234
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'},
241                                              admin_level=4,
242                                              fromarea=True,
243                                              isaddress=True,
244                                              rank_address=10,
245                                              distance=0.034)
246                             ])
247
248     result = api_impl.format_result(search, 'json', {})
249     js = json.loads(result)
250
251     assert js[outfield] == [{'localname': 'Trespass',
252                               'place_id': 3498,
253                               'osm_id': 442,
254                               'osm_type': 'R',
255                               'place_type': 'spec',
256                               'class': 'bnd',
257                               'type': 'note',
258                               'admin_level': 4,
259                               'rank_address': 10,
260                               'distance': 0.034,
261                               'isaddress': True}]
262
263
264 def test_search_details_grouped_hierarchy():
265     search = napi.DetailedResult(napi.SourceTable.PLACEX,
266                                  ('place', 'thing'),
267                                  napi.Point(1.0, 2.0),
268                                  parented_rows =
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'},
275                                              admin_level=4,
276                                              fromarea=True,
277                                              isaddress=True,
278                                              rank_address=10,
279                                              distance=0.034)
280                                      ])
281
282     result = api_impl.format_result(search, 'json', {'group_hierarchy': True})
283     js = json.loads(result)
284
285     assert js['hierarchy'] == {'note': [{'localname': 'Trespass',
286                               'place_id': 3498,
287                               'osm_id': 442,
288                               'osm_type': 'R',
289                               'place_type': 'spec',
290                               'class': 'bnd',
291                               'type': 'note',
292                               'admin_level': 4,
293                               'rank_address': 10,
294                               'distance': 0.034,
295                               'isaddress': True}]}
296
297
298 def test_search_details_keywords_name():
299     search = napi.DetailedResult(napi.SourceTable.PLACEX,
300                                  ('place', 'thing'),
301                                  napi.Point(1.0, 2.0),
302                                  name_keywords=[
303                                      napi.WordInfo(23, 'foo', 'mefoo'),
304                                      napi.WordInfo(24, 'foo', 'bafoo')])
305
306     result = api_impl.format_result(search, 'json', {'keywords': True})
307     js = json.loads(result)
308
309     assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'},
310                                       {'id': 24, 'token': 'foo'}],
311                               'address': []}
312
313
314 def test_search_details_keywords_address():
315     search = napi.DetailedResult(napi.SourceTable.PLACEX,
316                                  ('place', 'thing'),
317                                  napi.Point(1.0, 2.0),
318                                  address_keywords=[
319                                      napi.WordInfo(23, 'foo', 'mefoo'),
320                                      napi.WordInfo(24, 'foo', 'bafoo')])
321
322     result = api_impl.format_result(search, 'json', {'keywords': True})
323     js = json.loads(result)
324
325     assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'},
326                                       {'id': 24, 'token': 'foo'}],
327                               'name': []}
328