]> git.openstreetmap.org Git - nominatim.git/blob - test/python/api/search/test_search_postcode.py
enable flake for Python tests
[nominatim.git] / test / python / api / search / test_search_postcode.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 running the postcode searcher.
9 """
10 import pytest
11
12 import nominatim_api as napi
13 from nominatim_api.types import SearchDetails
14 from nominatim_api.search.db_searches import PostcodeSearch
15 from nominatim_api.search.db_search_fields import WeightedStrings, FieldLookup, \
16                                                   FieldRanking, RankedTokens
17
18
19 def run_search(apiobj, frontend, global_penalty, pcs, pc_penalties=None,
20                ccodes=[], lookup=[], ranking=[], details=SearchDetails()):
21     if pc_penalties is None:
22         pc_penalties = [0.0] * len(pcs)
23
24     class MySearchData:
25         penalty = global_penalty
26         postcodes = WeightedStrings(pcs, pc_penalties)
27         countries = WeightedStrings(ccodes, [0.0] * len(ccodes))
28         lookups = lookup
29         rankings = ranking
30
31     search = PostcodeSearch(0.0, MySearchData())
32
33     api = frontend(apiobj, options=['search'])
34
35     async def run():
36         async with api._async_api.begin() as conn:
37             return await search.lookup(conn, details)
38
39     return api._loop.run_until_complete(run())
40
41
42 def test_postcode_only_search(apiobj, frontend):
43     apiobj.add_postcode(place_id=100, country_code='ch', postcode='12345')
44     apiobj.add_postcode(place_id=101, country_code='pl', postcode='12 345')
45
46     results = run_search(apiobj, frontend, 0.3, ['12345', '12 345'], [0.0, 0.1])
47
48     assert len(results) == 2
49     assert [r.place_id for r in results] == [100, 101]
50
51
52 def test_postcode_with_country(apiobj, frontend):
53     apiobj.add_postcode(place_id=100, country_code='ch', postcode='12345')
54     apiobj.add_postcode(place_id=101, country_code='pl', postcode='12 345')
55
56     results = run_search(apiobj, frontend, 0.3, ['12345', '12 345'], [0.0, 0.1],
57                          ccodes=['de', 'pl'])
58
59     assert len(results) == 1
60     assert results[0].place_id == 101
61
62
63 def test_postcode_area(apiobj, frontend):
64     apiobj.add_postcode(place_id=100, country_code='ch', postcode='12345')
65     apiobj.add_placex(place_id=200, country_code='ch', postcode='12345',
66                       osm_type='R', osm_id=34, class_='boundary', type='postal_code',
67                       geometry='POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')
68
69     results = run_search(apiobj, frontend, 0.3, ['12345'], [0.0])
70
71     assert len(results) == 1
72     assert results[0].place_id == 200
73     assert results[0].bbox.area == 1
74
75
76 class TestPostcodeSearchWithAddress:
77
78     @pytest.fixture(autouse=True)
79     def fill_database(self, apiobj):
80         apiobj.add_postcode(place_id=100, country_code='ch',
81                             parent_place_id=1000, postcode='12345',
82                             geometry='POINT(17 5)')
83         apiobj.add_postcode(place_id=101, country_code='pl',
84                             parent_place_id=2000, postcode='12345',
85                             geometry='POINT(-45 7)')
86         apiobj.add_placex(place_id=1000, class_='place', type='village',
87                           rank_search=22, rank_address=22,
88                           country_code='ch')
89         apiobj.add_search_name(1000, names=[1, 2, 10, 11],
90                                search_rank=22, address_rank=22,
91                                country_code='ch')
92         apiobj.add_placex(place_id=2000, class_='place', type='village',
93                           rank_search=22, rank_address=22,
94                           country_code='pl')
95         apiobj.add_search_name(2000, names=[1, 2, 20, 21],
96                                search_rank=22, address_rank=22,
97                                country_code='pl')
98
99     def test_lookup_both(self, apiobj, frontend):
100         lookup = FieldLookup('name_vector', [1, 2], 'restrict')
101         ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
102
103         results = run_search(apiobj, frontend, 0.1, ['12345'], lookup=[lookup], ranking=[ranking])
104
105         assert [r.place_id for r in results] == [100, 101]
106
107     def test_restrict_by_name(self, apiobj, frontend):
108         lookup = FieldLookup('name_vector', [10], 'restrict')
109
110         results = run_search(apiobj, frontend, 0.1, ['12345'], lookup=[lookup])
111
112         assert [r.place_id for r in results] == [100]
113
114     @pytest.mark.parametrize('coord,place_id', [((16.5, 5), 100),
115                                                 ((-45.1, 7.004), 101)])
116     def test_lookup_near(self, apiobj, frontend, coord, place_id):
117         lookup = FieldLookup('name_vector', [1, 2], 'restrict')
118         ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
119
120         results = run_search(apiobj, frontend, 0.1, ['12345'],
121                              lookup=[lookup], ranking=[ranking],
122                              details=SearchDetails(near=napi.Point(*coord),
123                                                    near_radius=0.6))
124
125         assert [r.place_id for r in results] == [place_id]
126
127     @pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON,
128                                       napi.GeometryFormat.KML,
129                                       napi.GeometryFormat.SVG,
130                                       napi.GeometryFormat.TEXT])
131     def test_return_geometries(self, apiobj, frontend, geom):
132         results = run_search(apiobj, frontend, 0.1, ['12345'],
133                              details=SearchDetails(geometry_output=geom))
134
135         assert results
136         assert all(geom.name.lower() in r.geometry for r in results)
137
138     @pytest.mark.parametrize('viewbox, rids', [('-46,6,-44,8', [101, 100]),
139                                                ('16,4,18,6', [100, 101])])
140     def test_prefer_viewbox(self, apiobj, frontend, viewbox, rids):
141         results = run_search(apiobj, frontend, 0.1, ['12345'],
142                              details=SearchDetails.from_kwargs({'viewbox': viewbox}))
143
144         assert [r.place_id for r in results] == rids
145
146     @pytest.mark.parametrize('viewbox, rid', [('-46,6,-44,8', 101),
147                                               ('16,4,18,6', 100)])
148     def test_restrict_to_viewbox(self, apiobj, frontend, viewbox, rid):
149         results = run_search(apiobj, frontend, 0.1, ['12345'],
150                              details=SearchDetails.from_kwargs({'viewbox': viewbox,
151                                                                 'bounded_viewbox': True}))
152
153         assert [r.place_id for r in results] == [rid]
154
155     @pytest.mark.parametrize('coord,rids', [((17.05, 5), [100, 101]),
156                                             ((-45, 7.1), [101, 100])])
157     def test_prefer_near(self, apiobj, frontend, coord, rids):
158         results = run_search(apiobj, frontend, 0.1, ['12345'],
159                              details=SearchDetails(near=napi.Point(*coord)))
160
161         assert [r.place_id for r in results] == rids
162
163     @pytest.mark.parametrize('pid,rid', [(100, 101), (101, 100)])
164     def test_exclude(self, apiobj, frontend, pid, rid):
165         results = run_search(apiobj, frontend, 0.1, ['12345'],
166                              details=SearchDetails(excluded=[pid]))
167
168         assert [r.place_id for r in results] == [rid]