]> git.openstreetmap.org Git - nominatim.git/blob - test/python/api/test_result_formatting_v1.py
add output formatters for ReverseResults
[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             'localname': '',
79             'calculated_importance': pytest.approx(0.0000001),
80             'rank_address': 30,
81             'rank_search': 30,
82             'isarea': False,
83             'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]},
84             'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]},
85            }
86
87
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),
94                   place_id=37563,
95                   parent_place_id=114,
96                   linked_place_id=55693,
97                   osm_object=('W', 442100),
98                   admin_level=14,
99                   names={'name': 'Bank', 'name:fr': 'Banque'},
100                   address={'city': 'Niento', 'housenumber': '  3'},
101                   extratags={'atm': 'yes'},
102                   housenumber='3',
103                   postcode='556 X23',
104                   wikipedia='en:Bank',
105                   rank_address=29,
106                   rank_search=28,
107                   importance=0.0443,
108                   country_code='ll',
109                   indexed_date = import_date
110                   )
111
112     result = api_impl.format_result(search, 'json', {})
113
114     assert json.loads(result) == \
115            {'place_id': 37563,
116             'parent_place_id': 114,
117             'osm_type': 'W',
118             'osm_id': 442100,
119             'category': 'amenity',
120             'type': 'bank',
121             'admin_level': 14,
122             'localname': 'Bank',
123             'names': {'name': 'Bank', 'name:fr': 'Banque'},
124             'addresstags': {'city': 'Niento', 'housenumber': '  3'},
125             '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',
133             'rank_address': 29,
134             'rank_search': 28,
135             'isarea': False,
136             'centroid': {'type': 'Point', 'coordinates': [56.947, -87.44]},
137             'geometry': {'type': 'Point', 'coordinates': [56.947, -87.44]},
138            }
139
140
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,
147                                ('place', 'thing'),
148                                napi.Point(1.0, 2.0),
149                                geometry={'type': gtype})
150
151     result = api_impl.format_result(search, 'json', {})
152     js = json.loads(result)
153
154     assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
155     assert js['isarea'] == isarea
156
157
158 def test_search_details_with_geometry():
159     search = napi.DetailedResult(napi.SourceTable.PLACEX,
160                                  ('place', 'thing'),
161                                  napi.Point(1.0, 2.0),
162                                  geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
163
164     result = api_impl.format_result(search, 'json', {})
165     js = json.loads(result)
166
167     assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
168     assert js['isarea'] == False
169
170
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))
175
176     result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'})
177     js = json.loads(result)
178
179     assert js['icon'] == 'foo/food_restaurant.p.20.png'
180
181
182 def test_search_details_with_icon_not_available():
183     search = napi.DetailedResult(napi.SourceTable.PLACEX,
184                                  ('amenity', 'tree'),
185                                  napi.Point(1.0, 2.0))
186
187     result = api_impl.format_result(search, 'json', {'icon_base_url': 'foo'})
188     js = json.loads(result)
189
190     assert 'icon' not in js
191
192
193 def test_search_details_with_address_minimal():
194     search = napi.DetailedResult(napi.SourceTable.PLACEX,
195                                  ('place', 'thing'),
196                                  napi.Point(1.0, 2.0),
197                                  address_rows=[
198                                    napi.AddressLine(place_id=None,
199                                                     osm_object=None,
200                                                     category=('bnd', 'note'),
201                                                     names={},
202                                                     extratags=None,
203                                                     admin_level=None,
204                                                     fromarea=False,
205                                                     isaddress=False,
206                                                     rank_address=10,
207                                                     distance=0.0)
208                                  ])
209
210     result = api_impl.format_result(search, 'json', {})
211     js = json.loads(result)
212
213     assert js['address'] == [{'localname': '',
214                               'class': 'bnd',
215                               'type': 'note',
216                               'rank_address': 10,
217                               'distance': 0.0,
218                               'isaddress': False}]
219
220
221 @pytest.mark.parametrize('field,outfield', [('address_rows', 'address'),
222                                             ('linked_rows', 'linked_places'),
223                                             ('parented_rows', 'hierarchy')
224                                            ])
225 def test_search_details_with_further_infos(field, outfield):
226     search = napi.DetailedResult(napi.SourceTable.PLACEX,
227                                  ('place', 'thing'),
228                                  napi.Point(1.0, 2.0))
229
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'},
236                                              admin_level=4,
237                                              fromarea=True,
238                                              isaddress=True,
239                                              rank_address=10,
240                                              distance=0.034)
241                             ])
242
243     result = api_impl.format_result(search, 'json', {})
244     js = json.loads(result)
245
246     assert js[outfield] == [{'localname': 'Trespass',
247                               'place_id': 3498,
248                               'osm_id': 442,
249                               'osm_type': 'R',
250                               'place_type': 'spec',
251                               'class': 'bnd',
252                               'type': 'note',
253                               'admin_level': 4,
254                               'rank_address': 10,
255                               'distance': 0.034,
256                               'isaddress': True}]
257
258
259 def test_search_details_grouped_hierarchy():
260     search = napi.DetailedResult(napi.SourceTable.PLACEX,
261                                  ('place', 'thing'),
262                                  napi.Point(1.0, 2.0),
263                                  parented_rows =
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'},
270                                              admin_level=4,
271                                              fromarea=True,
272                                              isaddress=True,
273                                              rank_address=10,
274                                              distance=0.034)
275                                      ])
276
277     result = api_impl.format_result(search, 'json', {'group_hierarchy': True})
278     js = json.loads(result)
279
280     assert js['hierarchy'] == {'note': [{'localname': 'Trespass',
281                               'place_id': 3498,
282                               'osm_id': 442,
283                               'osm_type': 'R',
284                               'place_type': 'spec',
285                               'class': 'bnd',
286                               'type': 'note',
287                               'admin_level': 4,
288                               'rank_address': 10,
289                               'distance': 0.034,
290                               'isaddress': True}]}
291
292
293 def test_search_details_keywords_name():
294     search = napi.DetailedResult(napi.SourceTable.PLACEX,
295                                  ('place', 'thing'),
296                                  napi.Point(1.0, 2.0),
297                                  name_keywords=[
298                                      napi.WordInfo(23, 'foo', 'mefoo'),
299                                      napi.WordInfo(24, 'foo', 'bafoo')])
300
301     result = api_impl.format_result(search, 'json', {'keywords': True})
302     js = json.loads(result)
303
304     assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'},
305                                       {'id': 24, 'token': 'foo'}],
306                               'address': []}
307
308
309 def test_search_details_keywords_address():
310     search = napi.DetailedResult(napi.SourceTable.PLACEX,
311                                  ('place', 'thing'),
312                                  napi.Point(1.0, 2.0),
313                                  address_keywords=[
314                                      napi.WordInfo(23, 'foo', 'mefoo'),
315                                      napi.WordInfo(24, 'foo', 'bafoo')])
316
317     result = api_impl.format_result(search, 'json', {'keywords': True})
318     js = json.loads(result)
319
320     assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'},
321                                       {'id': 24, 'token': 'foo'}],
322                               'name': []}
323