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