From: Sarah Hoffmann Date: Sun, 9 Mar 2025 14:33:24 +0000 (+0100) Subject: enable flake for Python tests X-Git-Url: https://git.openstreetmap.org./nominatim.git/commitdiff_plain/4cc788f69e1191d2dd985aeac143597566529f24 enable flake for Python tests --- diff --git a/.flake8 b/.flake8 index 82d77ed3..f3e0db56 100644 --- a/.flake8 +++ b/.flake8 @@ -6,3 +6,5 @@ extend-ignore = E711 per-file-ignores = __init__.py: F401 + test/python/utils/test_json_writer.py: E131 + test/python/conftest.py: E402 diff --git a/Makefile b/Makefile index 9e914850..72072a59 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ pytest: pytest test/python lint: - flake8 src + flake8 src test/python bdd: cd test/bdd; behave -DREMOVE_TEMPLATE=1 diff --git a/test/python/api/conftest.py b/test/python/api/conftest.py index 3ca0720b..bde0afc4 100644 --- a/test/python/api/conftest.py +++ b/test/python/api/conftest.py @@ -2,14 +2,13 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Helper fixtures for API call tests. """ import pytest import pytest_asyncio -import time import datetime as dt import sqlalchemy as sa @@ -20,27 +19,25 @@ from nominatim_api.search.query_analyzer_factory import make_query_analyzer from nominatim_db.tools import convert_sqlite import nominatim_api.logging as loglib + class APITester: def __init__(self): self.api = napi.NominatimAPI() self.async_to_sync(self.api._async_api.setup_database()) - def async_to_sync(self, func): """ Run an asynchronous function until completion using the internal loop of the API. """ return self.api._loop.run_until_complete(func) - def add_data(self, table, data): """ Insert data into the given table. """ sql = getattr(self.api._async_api._tables, table).insert() self.async_to_sync(self.exec_async(sql, data)) - def add_placex(self, **kw): name = kw.get('name') if isinstance(name, str): @@ -50,30 +47,29 @@ class APITester: geometry = kw.get('geometry', 'POINT(%f %f)' % centroid) self.add_data('placex', - {'place_id': kw.get('place_id', 1000), - 'osm_type': kw.get('osm_type', 'W'), - 'osm_id': kw.get('osm_id', 4), - 'class_': kw.get('class_', 'highway'), - 'type': kw.get('type', 'residential'), - 'name': name, - 'address': kw.get('address'), - 'extratags': kw.get('extratags'), - 'parent_place_id': kw.get('parent_place_id'), - 'linked_place_id': kw.get('linked_place_id'), - 'admin_level': kw.get('admin_level', 15), - 'country_code': kw.get('country_code'), - 'housenumber': kw.get('housenumber'), - 'postcode': kw.get('postcode'), - 'wikipedia': kw.get('wikipedia'), - 'rank_search': kw.get('rank_search', 30), - 'rank_address': kw.get('rank_address', 30), - 'importance': kw.get('importance'), - 'centroid': 'POINT(%f %f)' % centroid, - 'indexed_status': kw.get('indexed_status', 0), - 'indexed_date': kw.get('indexed_date', - dt.datetime(2022, 12, 7, 14, 14, 46, 0)), - 'geometry': geometry}) - + {'place_id': kw.get('place_id', 1000), + 'osm_type': kw.get('osm_type', 'W'), + 'osm_id': kw.get('osm_id', 4), + 'class_': kw.get('class_', 'highway'), + 'type': kw.get('type', 'residential'), + 'name': name, + 'address': kw.get('address'), + 'extratags': kw.get('extratags'), + 'parent_place_id': kw.get('parent_place_id'), + 'linked_place_id': kw.get('linked_place_id'), + 'admin_level': kw.get('admin_level', 15), + 'country_code': kw.get('country_code'), + 'housenumber': kw.get('housenumber'), + 'postcode': kw.get('postcode'), + 'wikipedia': kw.get('wikipedia'), + 'rank_search': kw.get('rank_search', 30), + 'rank_address': kw.get('rank_address', 30), + 'importance': kw.get('importance'), + 'centroid': 'POINT(%f %f)' % centroid, + 'indexed_status': kw.get('indexed_status', 0), + 'indexed_date': kw.get('indexed_date', + dt.datetime(2022, 12, 7, 14, 14, 46, 0)), + 'geometry': geometry}) def add_address_placex(self, object_id, **kw): self.add_placex(**kw) @@ -85,46 +81,42 @@ class APITester: 'fromarea': kw.get('fromarea', False), 'isaddress': kw.get('isaddress', True)}) - def add_osmline(self, **kw): self.add_data('osmline', - {'place_id': kw.get('place_id', 10000), - 'osm_id': kw.get('osm_id', 4004), - 'parent_place_id': kw.get('parent_place_id'), - 'indexed_date': kw.get('indexed_date', - dt.datetime(2022, 12, 7, 14, 14, 46, 0)), - 'startnumber': kw.get('startnumber', 2), - 'endnumber': kw.get('endnumber', 6), - 'step': kw.get('step', 2), - 'address': kw.get('address'), - 'postcode': kw.get('postcode'), - 'country_code': kw.get('country_code'), - 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')}) - + {'place_id': kw.get('place_id', 10000), + 'osm_id': kw.get('osm_id', 4004), + 'parent_place_id': kw.get('parent_place_id'), + 'indexed_date': kw.get('indexed_date', + dt.datetime(2022, 12, 7, 14, 14, 46, 0)), + 'startnumber': kw.get('startnumber', 2), + 'endnumber': kw.get('endnumber', 6), + 'step': kw.get('step', 2), + 'address': kw.get('address'), + 'postcode': kw.get('postcode'), + 'country_code': kw.get('country_code'), + 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')}) def add_tiger(self, **kw): self.add_data('tiger', - {'place_id': kw.get('place_id', 30000), - 'parent_place_id': kw.get('parent_place_id'), - 'startnumber': kw.get('startnumber', 2), - 'endnumber': kw.get('endnumber', 6), - 'step': kw.get('step', 2), - 'postcode': kw.get('postcode'), - 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')}) - + {'place_id': kw.get('place_id', 30000), + 'parent_place_id': kw.get('parent_place_id'), + 'startnumber': kw.get('startnumber', 2), + 'endnumber': kw.get('endnumber', 6), + 'step': kw.get('step', 2), + 'postcode': kw.get('postcode'), + 'linegeo': kw.get('geometry', 'LINESTRING(1.1 -0.2, 1.09 -0.22)')}) def add_postcode(self, **kw): self.add_data('postcode', - {'place_id': kw.get('place_id', 1000), - 'parent_place_id': kw.get('parent_place_id'), - 'country_code': kw.get('country_code'), - 'postcode': kw.get('postcode'), - 'rank_search': kw.get('rank_search', 20), - 'rank_address': kw.get('rank_address', 22), - 'indexed_date': kw.get('indexed_date', - dt.datetime(2022, 12, 7, 14, 14, 46, 0)), - 'geometry': kw.get('geometry', 'POINT(23 34)')}) - + {'place_id': kw.get('place_id', 1000), + 'parent_place_id': kw.get('parent_place_id'), + 'country_code': kw.get('country_code'), + 'postcode': kw.get('postcode'), + 'rank_search': kw.get('rank_search', 20), + 'rank_address': kw.get('rank_address', 22), + 'indexed_date': kw.get('indexed_date', + dt.datetime(2022, 12, 7, 14, 14, 46, 0)), + 'geometry': kw.get('geometry', 'POINT(23 34)')}) def add_country(self, country_code, geometry): self.add_data('country_grid', @@ -132,14 +124,12 @@ class APITester: 'area': 0.1, 'geometry': geometry}) - def add_country_name(self, country_code, names, partition=0): self.add_data('country_name', {'country_code': country_code, 'name': names, 'partition': partition}) - def add_search_name(self, place_id, **kw): centroid = kw.get('centroid', (23.0, 34.0)) self.add_data('search_name', @@ -152,7 +142,6 @@ class APITester: 'country_code': kw.get('country_code', 'xx'), 'centroid': 'POINT(%f %f)' % centroid}) - def add_class_type_table(self, cls, typ): self.async_to_sync( self.exec_async(sa.text(f"""CREATE TABLE place_classtype_{cls}_{typ} @@ -160,7 +149,6 @@ class APITester: WHERE class = '{cls}' AND type = '{typ}') """))) - def add_word_table(self, content): data = [dict(zip(['word_id', 'word_token', 'type', 'word', 'info'], c)) for c in content] @@ -176,12 +164,10 @@ class APITester: self.async_to_sync(_do_sql()) - async def exec_async(self, sql, *args, **kwargs): async with self.api._async_api.begin() as conn: return await conn.execute(sql, *args, **kwargs) - async def create_tables(self): async with self.api._async_api._engine.begin() as conn: await conn.run_sync(self.api._async_api._tables.meta.create_all) @@ -212,11 +198,12 @@ def frontend(request, event_loop, tmp_path): db = str(tmp_path / 'test_nominatim_python_unittest.sqlite') def mkapi(apiobj, options={'reverse'}): - apiobj.add_data('properties', - [{'property': 'tokenizer', 'value': 'icu'}, - {'property': 'tokenizer_import_normalisation', 'value': ':: lower();'}, - {'property': 'tokenizer_import_transliteration', 'value': "'1' > '/1/'; 'ä' > 'ä '"}, - ]) + apiobj.add_data( + 'properties', + [{'property': 'tokenizer', 'value': 'icu'}, + {'property': 'tokenizer_import_normalisation', 'value': ':: lower();'}, + {'property': 'tokenizer_import_transliteration', + 'value': "'1' > '/1/'; 'ä' > 'ä '"}]) async def _do_sql(): async with apiobj.api._async_api.begin() as conn: diff --git a/test/python/api/fake_adaptor.py b/test/python/api/fake_adaptor.py index 4b64c17d..a3a3bcf9 100644 --- a/test/python/api/fake_adaptor.py +++ b/test/python/api/fake_adaptor.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Provides dummy implementations of ASGIAdaptor for testing. @@ -13,6 +13,7 @@ import nominatim_api.v1.server_glue as glue from nominatim_api.v1.format import dispatch as formatting from nominatim_api.config import Configuration + class FakeError(BaseException): def __init__(self, msg, status): @@ -22,8 +23,10 @@ class FakeError(BaseException): def __str__(self): return f'{self.status} -- {self.msg}' + FakeResponse = namedtuple('FakeResponse', ['status', 'output', 'content_type']) + class FakeAdaptor(glue.ASGIAdaptor): def __init__(self, params=None, headers=None, config=None): @@ -31,23 +34,18 @@ class FakeAdaptor(glue.ASGIAdaptor): self.headers = headers or {} self._config = config or Configuration(None) - def get(self, name, default=None): return self.params.get(name, default) - def get_header(self, name, default=None): return self.headers.get(name, default) - def error(self, msg, status=400): return FakeError(msg, status) - def create_response(self, status, output, num_results): return FakeResponse(status, output, self.content_type) - def base_uri(self): return 'http://test' @@ -56,5 +54,3 @@ class FakeAdaptor(glue.ASGIAdaptor): def formatting(self): return formatting - - diff --git a/test/python/api/query_processing/test_normalize.py b/test/python/api/query_processing/test_normalize.py index 12a8de2a..35f5fcd7 100644 --- a/test/python/api/query_processing/test_normalize.py +++ b/test/python/api/query_processing/test_normalize.py @@ -2,21 +2,18 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for normalizing search queries. """ -from pathlib import Path - -import pytest - from icu import Transliterator import nominatim_api.search.query as qmod from nominatim_api.query_preprocessing.config import QueryConfig from nominatim_api.query_preprocessing import normalize + def run_preprocessor_on(query, norm): normalizer = Transliterator.createFromRules("normalization", norm) proc = normalize.create(QueryConfig().set_normalizer(normalizer)) diff --git a/test/python/api/query_processing/test_split_japanese_phrases.py b/test/python/api/query_processing/test_split_japanese_phrases.py index 51d592e3..30f22e7b 100644 --- a/test/python/api/query_processing/test_split_japanese_phrases.py +++ b/test/python/api/query_processing/test_split_japanese_phrases.py @@ -7,16 +7,13 @@ """ Tests for japanese phrase splitting. """ -from pathlib import Path - import pytest -from icu import Transliterator - import nominatim_api.search.query as qmod from nominatim_api.query_preprocessing.config import QueryConfig from nominatim_api.query_preprocessing import split_japanese_phrases + def run_preprocessor_on(query): proc = split_japanese_phrases.create(QueryConfig().set_normalizer(None)) diff --git a/test/python/api/search/test_api_search_query.py b/test/python/api/search/test_api_search_query.py index 08a1f7aa..c9e8de93 100644 --- a/test/python/api/search/test_api_search_query.py +++ b/test/python/api/search/test_api_search_query.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for tokenized query data structures. @@ -11,6 +11,7 @@ import pytest from nominatim_api.search import query + class MyToken(query.Token): def get_category(self): @@ -21,9 +22,11 @@ def mktoken(tid: int): return MyToken(penalty=3.0, token=tid, count=1, addr_count=1, lookup_word='foo') + @pytest.fixture def qnode(): - return query.QueryNode(query.BREAK_PHRASE, query.PHRASE_ANY, 0.0 ,'', '') + return query.QueryNode(query.BREAK_PHRASE, query.PHRASE_ANY, 0.0, '', '') + @pytest.mark.parametrize('ptype,ttype', [(query.PHRASE_ANY, 'W'), (query.PHRASE_AMENITY, 'Q'), @@ -132,4 +135,3 @@ def test_query_struct_amenity_two_words(): assert len(q.get_tokens(query.TokenRange(1, 2), query.TOKEN_PARTIAL)) == 1 assert len(q.get_tokens(query.TokenRange(1, 2), query.TOKEN_NEAR_ITEM)) == 0 assert len(q.get_tokens(query.TokenRange(1, 2), query.TOKEN_QUALIFIER)) == 1 - diff --git a/test/python/api/search/test_db_search_builder.py b/test/python/api/search/test_db_search_builder.py index 49d5f303..be34fbea 100644 --- a/test/python/api/search/test_db_search_builder.py +++ b/test/python/api/search/test_db_search_builder.py @@ -16,6 +16,7 @@ from nominatim_api.search.token_assignment import TokenAssignment from nominatim_api.types import SearchDetails import nominatim_api.search.db_searches as dbs + class MyToken(Token): def get_category(self): return 'this', 'that' @@ -36,7 +37,6 @@ def make_query(*args): token=tid, count=1, addr_count=1, lookup_word=word)) - return q @@ -241,8 +241,7 @@ def test_name_and_address(): [(2, qmod.TOKEN_PARTIAL, [(2, 'b')]), (2, qmod.TOKEN_WORD, [(101, 'b')])], [(3, qmod.TOKEN_PARTIAL, [(3, 'c')]), - (3, qmod.TOKEN_WORD, [(102, 'c')])] - ) + (3, qmod.TOKEN_WORD, [(102, 'c')])]) builder = SearchBuilder(q, SearchDetails()) searches = list(builder.build(TokenAssignment(name=TokenRange(0, 1), @@ -267,8 +266,7 @@ def test_name_and_complex_address(): (3, qmod.TOKEN_WORD, [(101, 'bc')])], [(3, qmod.TOKEN_PARTIAL, [(3, 'c')])], [(4, qmod.TOKEN_PARTIAL, [(4, 'd')]), - (4, qmod.TOKEN_WORD, [(103, 'd')])] - ) + (4, qmod.TOKEN_WORD, [(103, 'd')])]) builder = SearchBuilder(q, SearchDetails()) searches = list(builder.build(TokenAssignment(name=TokenRange(0, 1), @@ -423,8 +421,8 @@ def test_infrequent_partials_in_name(): assert len(search.lookups) == 2 assert len(search.rankings) == 2 - assert set((l.column, l.lookup_type.__name__) for l in search.lookups) == \ - {('name_vector', 'LookupAll'), ('nameaddress_vector', 'Restrict')} + assert set((s.column, s.lookup_type.__name__) for s in search.lookups) == \ + {('name_vector', 'LookupAll'), ('nameaddress_vector', 'Restrict')} def test_frequent_partials_in_name_and_address(): @@ -435,10 +433,10 @@ def test_frequent_partials_in_name_and_address(): assert all(isinstance(s, dbs.PlaceSearch) for s in searches) searches.sort(key=lambda s: s.penalty) - assert set((l.column, l.lookup_type.__name__) for l in searches[0].lookups) == \ - {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')} - assert set((l.column, l.lookup_type.__name__) for l in searches[1].lookups) == \ - {('nameaddress_vector', 'LookupAll'), ('name_vector', 'LookupAll')} + assert set((s.column, s.lookup_type.__name__) for s in searches[0].lookups) == \ + {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')} + assert set((s.column, s.lookup_type.__name__) for s in searches[1].lookups) == \ + {('nameaddress_vector', 'LookupAll'), ('name_vector', 'LookupAll')} def test_too_frequent_partials_in_name_and_address(): @@ -449,5 +447,5 @@ def test_too_frequent_partials_in_name_and_address(): assert all(isinstance(s, dbs.PlaceSearch) for s in searches) searches.sort(key=lambda s: s.penalty) - assert set((l.column, l.lookup_type.__name__) for l in searches[0].lookups) == \ - {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')} + assert set((s.column, s.lookup_type.__name__) for s in searches[0].lookups) == \ + {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')} diff --git a/test/python/api/search/test_icu_query_analyzer.py b/test/python/api/search/test_icu_query_analyzer.py index fc200bca..72535026 100644 --- a/test/python/api/search/test_icu_query_analyzer.py +++ b/test/python/api/search/test_icu_query_analyzer.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for query analyzer for ICU tokenizer. @@ -16,7 +16,8 @@ import nominatim_api.search.query as qmod import nominatim_api.search.icu_tokenizer as tok from nominatim_api.logging import set_log_output, get_and_disable -async def add_word(conn, word_id, word_token, wtype, word, info = None): + +async def add_word(conn, word_id, word_token, wtype, word, info=None): t = conn.t.meta.tables['word'] await conn.execute(t.insert(), {'word_id': word_id, 'word_token': word_token, @@ -28,6 +29,7 @@ async def add_word(conn, word_id, word_token, wtype, word, info = None): def make_phrase(query): return [Phrase(qmod.PHRASE_ANY, s) for s in query.split(',')] + @pytest_asyncio.fixture async def conn(table_factory): """ Create an asynchronous SQLAlchemy engine for the test DB. @@ -102,8 +104,7 @@ async def test_splitting_in_transliteration(conn): @pytest.mark.asyncio @pytest.mark.parametrize('term,order', [('23456', ['P', 'H', 'W', 'w']), - ('3', ['H', 'W', 'w']) - ]) + ('3', ['H', 'W', 'w'])]) async def test_penalty_postcodes_and_housenumbers(conn, term, order): ana = await tok.create_query_analyzer(conn) @@ -120,6 +121,7 @@ async def test_penalty_postcodes_and_housenumbers(conn, term, order): assert [t[1] for t in torder] == order + @pytest.mark.asyncio async def test_category_words_only_at_beginning(conn): ana = await tok.create_query_analyzer(conn) diff --git a/test/python/api/search/test_postcode_parser.py b/test/python/api/search/test_postcode_parser.py index 284aba5b..38638e07 100644 --- a/test/python/api/search/test_postcode_parser.py +++ b/test/python/api/search/test_postcode_parser.py @@ -16,6 +16,7 @@ import pytest from nominatim_api.search.postcode_parser import PostcodeParser from nominatim_api.search.query import QueryStruct, PHRASE_ANY, PHRASE_POSTCODE, PHRASE_STREET + @pytest.fixture def pc_config(project_env): country_file = project_env.project_dir / 'country_settings.yaml' @@ -55,6 +56,7 @@ ky: return project_env + def mk_query(inp): query = QueryStruct([]) phrase_split = re.split(r"([ ,:'-])", inp) @@ -80,6 +82,7 @@ def test_simple_postcode(pc_config, query, pos): assert result == {(pos, pos + 1, '45325'), (pos, pos + 1, '453 25')} + def test_contained_postcode(pc_config): parser = PostcodeParser(pc_config) @@ -87,7 +90,6 @@ def test_contained_postcode(pc_config): (0, 2, '12345 DX')} - @pytest.mark.parametrize('query,frm,to', [('345987', 0, 1), ('345 987', 0, 2), ('Aina 345 987', 1, 3), ('Aina 23 345 987 ff', 2, 4)]) @@ -98,6 +100,7 @@ def test_postcode_with_space(pc_config, query, frm, to): assert result == {(frm, to, '345987')} + def test_overlapping_postcode(pc_config): parser = PostcodeParser(pc_config) @@ -131,6 +134,7 @@ def test_postcode_with_non_matching_country_prefix(pc_config): assert not parser.parse(mk_query('ky12233')) + def test_postcode_inside_postcode_phrase(pc_config): parser = PostcodeParser(pc_config) diff --git a/test/python/api/search/test_query.py b/test/python/api/search/test_query.py index bfed38df..09f25f8e 100644 --- a/test/python/api/search/test_query.py +++ b/test/python/api/search/test_query.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Test data types for search queries. @@ -11,14 +11,15 @@ import pytest import nominatim_api.search.query as nq + def test_token_range_equal(): assert nq.TokenRange(2, 3) == nq.TokenRange(2, 3) assert not (nq.TokenRange(2, 3) != nq.TokenRange(2, 3)) @pytest.mark.parametrize('lop,rop', [((1, 2), (3, 4)), - ((3, 4), (3, 5)), - ((10, 12), (11, 12))]) + ((3, 4), (3, 5)), + ((10, 12), (11, 12))]) def test_token_range_unequal(lop, rop): assert not (nq.TokenRange(*lop) == nq.TokenRange(*rop)) assert nq.TokenRange(*lop) != nq.TokenRange(*rop) @@ -28,17 +29,17 @@ def test_token_range_lt(): assert nq.TokenRange(1, 3) < nq.TokenRange(10, 12) assert nq.TokenRange(5, 6) < nq.TokenRange(7, 8) assert nq.TokenRange(1, 4) < nq.TokenRange(4, 5) - assert not(nq.TokenRange(5, 6) < nq.TokenRange(5, 6)) - assert not(nq.TokenRange(10, 11) < nq.TokenRange(4, 5)) + assert not (nq.TokenRange(5, 6) < nq.TokenRange(5, 6)) + assert not (nq.TokenRange(10, 11) < nq.TokenRange(4, 5)) def test_token_rankge_gt(): assert nq.TokenRange(3, 4) > nq.TokenRange(1, 2) assert nq.TokenRange(100, 200) > nq.TokenRange(10, 11) assert nq.TokenRange(10, 11) > nq.TokenRange(4, 10) - assert not(nq.TokenRange(5, 6) > nq.TokenRange(5, 6)) - assert not(nq.TokenRange(1, 2) > nq.TokenRange(3, 4)) - assert not(nq.TokenRange(4, 10) > nq.TokenRange(3, 5)) + assert not (nq.TokenRange(5, 6) > nq.TokenRange(5, 6)) + assert not (nq.TokenRange(1, 2) > nq.TokenRange(3, 4)) + assert not (nq.TokenRange(4, 10) > nq.TokenRange(3, 5)) def test_token_range_unimplemented_ops(): @@ -58,8 +59,7 @@ def test_query_extract_words(): words = q.extract_words(base_penalty=1.0) assert set(words.keys()) \ - == {'12', 'ab', 'hallo', '12 ab', 'ab 12', '12 ab 12'} + == {'12', 'ab', 'hallo', '12 ab', 'ab 12', '12 ab 12'} assert sorted(words['12']) == [nq.TokenRange(0, 1, 1.0), nq.TokenRange(2, 3, 1.0)] assert words['12 ab'] == [nq.TokenRange(0, 2, 1.1)] assert words['hallo'] == [nq.TokenRange(3, 4, 1.0)] - diff --git a/test/python/api/search/test_query_analyzer_factory.py b/test/python/api/search/test_query_analyzer_factory.py index 42220b55..933bdd1f 100644 --- a/test/python/api/search/test_query_analyzer_factory.py +++ b/test/python/api/search/test_query_analyzer_factory.py @@ -2,18 +2,17 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for query analyzer creation. """ -from pathlib import Path - import pytest from nominatim_api.search.query_analyzer_factory import make_query_analyzer from nominatim_api.search.icu_tokenizer import ICUQueryAnalyzer + @pytest.mark.asyncio async def test_import_icu_tokenizer(table_factory, api): table_factory('nominatim_properties', diff --git a/test/python/api/search/test_search_country.py b/test/python/api/search/test_search_country.py index 2109ecb0..46875a2c 100644 --- a/test/python/api/search/test_search_country.py +++ b/test/python/api/search/test_search_country.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for running the country searcher. @@ -48,6 +48,7 @@ def test_find_from_placex(apiobj, frontend): assert results[0].place_id == 55 assert results[0].accuracy == 0.8 + def test_find_from_fallback_countries(apiobj, frontend): apiobj.add_country('ro', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))') apiobj.add_country_name('ro', {'name': 'România'}) @@ -87,7 +88,6 @@ class TestCountryParameters: apiobj.add_country('ro', 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))') apiobj.add_country_name('ro', {'name': 'România'}) - @pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON, napi.GeometryFormat.KML, napi.GeometryFormat.SVG, @@ -100,7 +100,6 @@ class TestCountryParameters: assert len(results) == 1 assert geom.name.lower() in results[0].geometry - @pytest.mark.parametrize('pid,rids', [(76, [55]), (55, [])]) def test_exclude_place_id(self, apiobj, frontend, pid, rids): results = run_search(apiobj, frontend, 0.5, ['yw', 'ro'], @@ -108,7 +107,6 @@ class TestCountryParameters: assert [r.place_id for r in results] == rids - @pytest.mark.parametrize('viewbox,rids', [((9, 9, 11, 11), [55]), ((-10, -10, -3, -3), [])]) def test_bounded_viewbox_in_placex(self, apiobj, frontend, viewbox, rids): @@ -118,9 +116,8 @@ class TestCountryParameters: assert [r.place_id for r in results] == rids - @pytest.mark.parametrize('viewbox,numres', [((0, 0, 1, 1), 1), - ((-10, -10, -3, -3), 0)]) + ((-10, -10, -3, -3), 0)]) def test_bounded_viewbox_in_fallback(self, apiobj, frontend, viewbox, numres): results = run_search(apiobj, frontend, 0.5, ['ro'], details=SearchDetails.from_kwargs({'viewbox': viewbox, diff --git a/test/python/api/search/test_search_near.py b/test/python/api/search/test_search_near.py index 43098ddd..e9650168 100644 --- a/test/python/api/search/test_search_near.py +++ b/test/python/api/search/test_search_near.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for running the near searcher. @@ -12,8 +12,8 @@ import pytest import nominatim_api as napi from nominatim_api.types import SearchDetails from nominatim_api.search.db_searches import NearSearch, PlaceSearch -from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories,\ - FieldLookup, FieldRanking, RankedTokens +from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories, \ + FieldLookup from nominatim_api.search.db_search_lookups import LookupAll @@ -80,7 +80,6 @@ class TestNearSearch: apiobj.add_search_name(101, names=[56], country_code='mx', centroid=(-10.3, 56.9)) - def test_near_in_placex(self, apiobj, frontend): apiobj.add_placex(place_id=22, class_='amenity', type='bank', centroid=(5.6001, 4.2994)) @@ -91,7 +90,6 @@ class TestNearSearch: assert [r.place_id for r in results] == [22] - def test_multiple_types_near_in_placex(self, apiobj, frontend): apiobj.add_placex(place_id=22, class_='amenity', type='bank', importance=0.002, @@ -105,7 +103,6 @@ class TestNearSearch: assert [r.place_id for r in results] == [22, 23] - def test_near_in_classtype(self, apiobj, frontend): apiobj.add_placex(place_id=22, class_='amenity', type='bank', centroid=(5.6, 4.34)) @@ -118,7 +115,6 @@ class TestNearSearch: assert [r.place_id for r in results] == [22] - @pytest.mark.parametrize('cc,rid', [('us', 22), ('mx', 23)]) def test_restrict_by_country(self, apiobj, frontend, cc, rid): apiobj.add_placex(place_id=22, class_='amenity', type='bank', @@ -138,7 +134,6 @@ class TestNearSearch: assert [r.place_id for r in results] == [rid] - @pytest.mark.parametrize('excluded,rid', [(22, 122), (122, 22)]) def test_exclude_place_by_id(self, apiobj, frontend, excluded, rid): apiobj.add_placex(place_id=22, class_='amenity', type='bank', @@ -148,13 +143,11 @@ class TestNearSearch: centroid=(5.6001, 4.2994), country_code='us') - results = run_search(apiobj, frontend, 0.1, [('amenity', 'bank')], details=SearchDetails(excluded=[excluded])) assert [r.place_id for r in results] == [rid] - @pytest.mark.parametrize('layer,rids', [(napi.DataLayer.POI, [22]), (napi.DataLayer.MANMADE, [])]) def test_with_layer(self, apiobj, frontend, layer, rids): diff --git a/test/python/api/search/test_search_places.py b/test/python/api/search/test_search_places.py index c6ff16b8..ed0722c3 100644 --- a/test/python/api/search/test_search_places.py +++ b/test/python/api/search/test_search_places.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for running the generic place searcher. @@ -14,12 +14,13 @@ import pytest import nominatim_api as napi from nominatim_api.types import SearchDetails from nominatim_api.search.db_searches import PlaceSearch -from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories,\ +from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories, \ FieldLookup, FieldRanking, RankedTokens from nominatim_api.search.db_search_lookups import LookupAll, LookupAny, Restrict APIOPTIONS = ['search'] + def run_search(apiobj, frontend, global_penalty, lookup, ranking, count=2, hnrs=[], pcs=[], ccodes=[], quals=[], details=SearchDetails()): @@ -55,29 +56,27 @@ class TestNameOnlySearches: def fill_database(self, apiobj): apiobj.add_placex(place_id=100, country_code='us', centroid=(5.6, 4.3)) - apiobj.add_search_name(100, names=[1,2,10,11], country_code='us', + apiobj.add_search_name(100, names=[1, 2, 10, 11], country_code='us', centroid=(5.6, 4.3)) apiobj.add_placex(place_id=101, country_code='mx', centroid=(-10.3, 56.9)) - apiobj.add_search_name(101, names=[1,2,20,21], country_code='mx', + apiobj.add_search_name(101, names=[1, 2, 20, 21], country_code='mx', centroid=(-10.3, 56.9)) - @pytest.mark.parametrize('lookup_type', [LookupAll, Restrict]) @pytest.mark.parametrize('rank,res', [([10], [100, 101]), ([20], [101, 100])]) def test_lookup_all_match(self, apiobj, frontend, lookup_type, rank, res): - lookup = FieldLookup('name_vector', [1,2], lookup_type) + lookup = FieldLookup('name_vector', [1, 2], lookup_type) ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, rank)]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking]) assert [r.place_id for r in results] == res - @pytest.mark.parametrize('lookup_type', [LookupAll, Restrict]) def test_lookup_all_partial_match(self, apiobj, frontend, lookup_type): - lookup = FieldLookup('name_vector', [1,20], lookup_type) + lookup = FieldLookup('name_vector', [1, 20], lookup_type) ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [21])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking]) @@ -88,14 +87,13 @@ class TestNameOnlySearches: @pytest.mark.parametrize('rank,res', [([10], [100, 101]), ([20], [101, 100])]) def test_lookup_any_match(self, apiobj, frontend, rank, res): - lookup = FieldLookup('name_vector', [11,21], LookupAny) + lookup = FieldLookup('name_vector', [11, 21], LookupAny) ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, rank)]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking]) assert [r.place_id for r in results] == res - def test_lookup_any_partial_match(self, apiobj, frontend): lookup = FieldLookup('name_vector', [20], LookupAll) ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [21])]) @@ -105,19 +103,17 @@ class TestNameOnlySearches: assert len(results) == 1 assert results[0].place_id == 101 - @pytest.mark.parametrize('cc,res', [('us', 100), ('mx', 101)]) def test_lookup_restrict_country(self, apiobj, frontend, cc, res): - lookup = FieldLookup('name_vector', [1,2], LookupAll) + lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], ccodes=[cc]) assert [r.place_id for r in results] == [res] - def test_lookup_restrict_placeid(self, apiobj, frontend): - lookup = FieldLookup('name_vector', [1,2], LookupAll) + lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], @@ -125,7 +121,6 @@ class TestNameOnlySearches: assert [r.place_id for r in results] == [100] - @pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON, napi.GeometryFormat.KML, napi.GeometryFormat.SVG, @@ -139,7 +134,6 @@ class TestNameOnlySearches: assert geom.name.lower() in results[0].geometry - @pytest.mark.parametrize('factor,npoints', [(0.0, 3), (1.0, 2)]) def test_return_simplified_geometry(self, apiobj, frontend, factor, npoints): apiobj.add_placex(place_id=333, country_code='us', @@ -162,7 +156,6 @@ class TestNameOnlySearches: assert result.place_id == 333 assert len(geom['coordinates']) == npoints - @pytest.mark.parametrize('viewbox', ['5.0,4.0,6.0,5.0', '5.7,4.0,6.0,5.0']) @pytest.mark.parametrize('wcount,rids', [(2, [100, 101]), (20000, [100])]) def test_prefer_viewbox(self, apiobj, frontend, viewbox, wcount, rids): @@ -177,18 +170,16 @@ class TestNameOnlySearches: details=SearchDetails.from_kwargs({'viewbox': viewbox})) assert [r.place_id for r in results] == rids - @pytest.mark.parametrize('viewbox', ['5.0,4.0,6.0,5.0', '5.55,4.27,5.62,4.31']) def test_force_viewbox(self, apiobj, frontend, viewbox): lookup = FieldLookup('name_vector', [1, 2], LookupAll) - details=SearchDetails.from_kwargs({'viewbox': viewbox, - 'bounded_viewbox': True}) + details = SearchDetails.from_kwargs({'viewbox': viewbox, + 'bounded_viewbox': True}) results = run_search(apiobj, frontend, 0.1, [lookup], [], details=details) assert [r.place_id for r in results] == [100] - def test_prefer_near(self, apiobj, frontend): lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.4, [RankedTokens(0.0, [21])]) @@ -202,13 +193,12 @@ class TestNameOnlySearches: results.sort(key=lambda r: -r.importance) assert [r.place_id for r in results] == [100, 101] - @pytest.mark.parametrize('radius', [0.09, 0.11]) def test_force_near(self, apiobj, frontend, radius): lookup = FieldLookup('name_vector', [1, 2], LookupAll) - details=SearchDetails.from_kwargs({'near': '5.6,4.3', - 'near_radius': radius}) + details = SearchDetails.from_kwargs({'near': '5.6,4.3', + 'near_radius': radius}) results = run_search(apiobj, frontend, 0.1, [lookup], [], details=details) @@ -228,7 +218,7 @@ class TestStreetWithHousenumber: apiobj.add_placex(place_id=1000, class_='highway', type='residential', rank_search=26, rank_address=26, country_code='es') - apiobj.add_search_name(1000, names=[1,2,10,11], + apiobj.add_search_name(1000, names=[1, 2, 10, 11], search_rank=26, address_rank=26, country_code='es') apiobj.add_placex(place_id=91, class_='place', type='house', @@ -243,26 +233,24 @@ class TestStreetWithHousenumber: apiobj.add_placex(place_id=2000, class_='highway', type='residential', rank_search=26, rank_address=26, country_code='pt') - apiobj.add_search_name(2000, names=[1,2,20,21], + apiobj.add_search_name(2000, names=[1, 2, 20, 21], search_rank=26, address_rank=26, country_code='pt') - @pytest.mark.parametrize('hnr,res', [('20', [91, 1]), ('20 a', [1]), ('21', [2]), ('22', [2, 92]), ('24', [93]), ('25', [])]) def test_lookup_by_single_housenumber(self, apiobj, frontend, hnr, res): - lookup = FieldLookup('name_vector', [1,2], LookupAll) + lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=[hnr]) assert [r.place_id for r in results] == res + [1000, 2000] - @pytest.mark.parametrize('cc,res', [('es', [2, 1000]), ('pt', [92, 2000])]) def test_lookup_with_country_restriction(self, apiobj, frontend, cc, res): - lookup = FieldLookup('name_vector', [1,2], LookupAll) + lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'], @@ -270,9 +258,8 @@ class TestStreetWithHousenumber: assert [r.place_id for r in results] == res - def test_lookup_exclude_housenumber_placeid(self, apiobj, frontend): - lookup = FieldLookup('name_vector', [1,2], LookupAll) + lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'], @@ -280,9 +267,8 @@ class TestStreetWithHousenumber: assert [r.place_id for r in results] == [2, 1000, 2000] - def test_lookup_exclude_street_placeid(self, apiobj, frontend): - lookup = FieldLookup('name_vector', [1,2], LookupAll) + lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'], @@ -290,9 +276,8 @@ class TestStreetWithHousenumber: assert [r.place_id for r in results] == [2, 92, 2000] - def test_lookup_only_house_qualifier(self, apiobj, frontend): - lookup = FieldLookup('name_vector', [1,2], LookupAll) + lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'], @@ -300,9 +285,8 @@ class TestStreetWithHousenumber: assert [r.place_id for r in results] == [2, 92] - def test_lookup_only_street_qualifier(self, apiobj, frontend): - lookup = FieldLookup('name_vector', [1,2], LookupAll) + lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'], @@ -310,10 +294,9 @@ class TestStreetWithHousenumber: assert [r.place_id for r in results] == [1000, 2000] - @pytest.mark.parametrize('rank,found', [(26, True), (27, False), (30, False)]) def test_lookup_min_rank(self, apiobj, frontend, rank, found): - lookup = FieldLookup('name_vector', [1,2], LookupAll) + lookup = FieldLookup('name_vector', [1, 2], LookupAll) ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, [lookup], [ranking], hnrs=['22'], @@ -321,7 +304,6 @@ class TestStreetWithHousenumber: assert [r.place_id for r in results] == ([2, 92, 1000, 2000] if found else [2, 92]) - @pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON, napi.GeometryFormat.KML, napi.GeometryFormat.SVG, @@ -343,7 +325,7 @@ def test_very_large_housenumber(apiobj, frontend): apiobj.add_placex(place_id=2000, class_='highway', type='residential', rank_search=26, rank_address=26, country_code='pt') - apiobj.add_search_name(2000, names=[1,2], + apiobj.add_search_name(2000, names=[1, 2], search_rank=26, address_rank=26, country_code='pt') @@ -405,7 +387,6 @@ class TestInterpolations: centroid=(10.0, 10.00001), geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)') - @pytest.mark.parametrize('hnr,res', [('21', [992]), ('22', []), ('23', [991])]) def test_lookup_housenumber(self, apiobj, frontend, hnr, res): lookup = FieldLookup('name_vector', [111], LookupAll) @@ -414,7 +395,6 @@ class TestInterpolations: assert [r.place_id for r in results] == res + [990] - @pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON, napi.GeometryFormat.KML, napi.GeometryFormat.SVG, @@ -429,7 +409,6 @@ class TestInterpolations: assert geom.name.lower() in results[0].geometry - class TestTiger: @pytest.fixture(autouse=True) @@ -453,7 +432,6 @@ class TestTiger: centroid=(10.0, 10.00001), geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)') - @pytest.mark.parametrize('hnr,res', [('21', [992]), ('22', []), ('23', [991])]) def test_lookup_housenumber(self, apiobj, frontend, hnr, res): lookup = FieldLookup('name_vector', [111], LookupAll) @@ -462,7 +440,6 @@ class TestTiger: assert [r.place_id for r in results] == res + [990] - @pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON, napi.GeometryFormat.KML, napi.GeometryFormat.SVG, @@ -513,15 +490,15 @@ class TestLayersRank30: importance=0.0005, address_rank=0, search_rank=30) - - @pytest.mark.parametrize('layer,res', [(napi.DataLayer.ADDRESS, [223]), - (napi.DataLayer.POI, [224]), - (napi.DataLayer.ADDRESS | napi.DataLayer.POI, [223, 224]), - (napi.DataLayer.MANMADE, [225]), - (napi.DataLayer.RAILWAY, [226]), - (napi.DataLayer.NATURAL, [227]), - (napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, [225, 227]), - (napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, [225, 226])]) + @pytest.mark.parametrize('layer,res', + [(napi.DataLayer.ADDRESS, [223]), + (napi.DataLayer.POI, [224]), + (napi.DataLayer.ADDRESS | napi.DataLayer.POI, [223, 224]), + (napi.DataLayer.MANMADE, [225]), + (napi.DataLayer.RAILWAY, [226]), + (napi.DataLayer.NATURAL, [227]), + (napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, [225, 227]), + (napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, [225, 226])]) def test_layers_rank30(self, apiobj, frontend, layer, res): lookup = FieldLookup('name_vector', [34], LookupAny) diff --git a/test/python/api/search/test_search_poi.py b/test/python/api/search/test_search_poi.py index d4319a57..9387385e 100644 --- a/test/python/api/search/test_search_poi.py +++ b/test/python/api/search/test_search_poi.py @@ -2,14 +2,13 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for running the POI searcher. """ import pytest -import nominatim_api as napi from nominatim_api.types import SearchDetails from nominatim_api.search.db_searches import PoiSearch from nominatim_api.search.db_search_fields import WeightedStrings, WeightedCategories @@ -84,14 +83,12 @@ class TestPoiSearchWithRestrictions: else: self.args = {'near': '34.3, 56.100021', 'near_radius': 0.001} - def test_unrestricted(self, apiobj, frontend): results = run_search(apiobj, frontend, 0.1, [('highway', 'bus_stop')], [0.5], details=SearchDetails.from_kwargs(self.args)) assert [r.place_id for r in results] == [1, 2] - def test_restict_country(self, apiobj, frontend): results = run_search(apiobj, frontend, 0.1, [('highway', 'bus_stop')], [0.5], ccodes=['de', 'nz'], @@ -99,7 +96,6 @@ class TestPoiSearchWithRestrictions: assert [r.place_id for r in results] == [2] - def test_restrict_by_viewbox(self, apiobj, frontend): args = {'bounded_viewbox': True, 'viewbox': '34.299,56.0,34.3001,56.10001'} args.update(self.args) diff --git a/test/python/api/search/test_search_postcode.py b/test/python/api/search/test_search_postcode.py index 369e1504..529fb409 100644 --- a/test/python/api/search/test_search_postcode.py +++ b/test/python/api/search/test_search_postcode.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for running the postcode searcher. @@ -15,6 +15,7 @@ from nominatim_api.search.db_searches import PostcodeSearch from nominatim_api.search.db_search_fields import WeightedStrings, FieldLookup, \ FieldRanking, RankedTokens + def run_search(apiobj, frontend, global_penalty, pcs, pc_penalties=None, ccodes=[], lookup=[], ranking=[], details=SearchDetails()): if pc_penalties is None: @@ -85,26 +86,24 @@ class TestPostcodeSearchWithAddress: apiobj.add_placex(place_id=1000, class_='place', type='village', rank_search=22, rank_address=22, country_code='ch') - apiobj.add_search_name(1000, names=[1,2,10,11], + apiobj.add_search_name(1000, names=[1, 2, 10, 11], search_rank=22, address_rank=22, country_code='ch') apiobj.add_placex(place_id=2000, class_='place', type='village', rank_search=22, rank_address=22, country_code='pl') - apiobj.add_search_name(2000, names=[1,2,20,21], + apiobj.add_search_name(2000, names=[1, 2, 20, 21], search_rank=22, address_rank=22, country_code='pl') - def test_lookup_both(self, apiobj, frontend): - lookup = FieldLookup('name_vector', [1,2], 'restrict') + lookup = FieldLookup('name_vector', [1, 2], 'restrict') ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, ['12345'], lookup=[lookup], ranking=[ranking]) assert [r.place_id for r in results] == [100, 101] - def test_restrict_by_name(self, apiobj, frontend): lookup = FieldLookup('name_vector', [10], 'restrict') @@ -112,11 +111,10 @@ class TestPostcodeSearchWithAddress: assert [r.place_id for r in results] == [100] - @pytest.mark.parametrize('coord,place_id', [((16.5, 5), 100), ((-45.1, 7.004), 101)]) def test_lookup_near(self, apiobj, frontend, coord, place_id): - lookup = FieldLookup('name_vector', [1,2], 'restrict') + lookup = FieldLookup('name_vector', [1, 2], 'restrict') ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])]) results = run_search(apiobj, frontend, 0.1, ['12345'], @@ -126,7 +124,6 @@ class TestPostcodeSearchWithAddress: assert [r.place_id for r in results] == [place_id] - @pytest.mark.parametrize('geom', [napi.GeometryFormat.GEOJSON, napi.GeometryFormat.KML, napi.GeometryFormat.SVG, @@ -138,18 +135,16 @@ class TestPostcodeSearchWithAddress: assert results assert all(geom.name.lower() in r.geometry for r in results) - - @pytest.mark.parametrize('viewbox, rids', [('-46,6,-44,8', [101,100]), - ('16,4,18,6', [100,101])]) + @pytest.mark.parametrize('viewbox, rids', [('-46,6,-44,8', [101, 100]), + ('16,4,18,6', [100, 101])]) def test_prefer_viewbox(self, apiobj, frontend, viewbox, rids): results = run_search(apiobj, frontend, 0.1, ['12345'], details=SearchDetails.from_kwargs({'viewbox': viewbox})) assert [r.place_id for r in results] == rids - @pytest.mark.parametrize('viewbox, rid', [('-46,6,-44,8', 101), - ('16,4,18,6', 100)]) + ('16,4,18,6', 100)]) def test_restrict_to_viewbox(self, apiobj, frontend, viewbox, rid): results = run_search(apiobj, frontend, 0.1, ['12345'], details=SearchDetails.from_kwargs({'viewbox': viewbox, @@ -157,7 +152,6 @@ class TestPostcodeSearchWithAddress: assert [r.place_id for r in results] == [rid] - @pytest.mark.parametrize('coord,rids', [((17.05, 5), [100, 101]), ((-45, 7.1), [101, 100])]) def test_prefer_near(self, apiobj, frontend, coord, rids): @@ -166,7 +160,6 @@ class TestPostcodeSearchWithAddress: assert [r.place_id for r in results] == rids - @pytest.mark.parametrize('pid,rid', [(100, 101), (101, 100)]) def test_exclude(self, apiobj, frontend, pid, rid): results = run_search(apiobj, frontend, 0.1, ['12345'], diff --git a/test/python/api/search/test_token_assignment.py b/test/python/api/search/test_token_assignment.py index fff8d471..2ffba335 100644 --- a/test/python/api/search/test_token_assignment.py +++ b/test/python/api/search/test_token_assignment.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Test for creation of token assignments from tokenized queries. @@ -11,7 +11,10 @@ import pytest from nominatim_api.search.query import QueryStruct, Phrase, TokenRange, Token import nominatim_api.search.query as qmod -from nominatim_api.search.token_assignment import yield_token_assignments, TokenAssignment, PENALTY_TOKENCHANGE +from nominatim_api.search.token_assignment import (yield_token_assignments, + TokenAssignment, + PENALTY_TOKENCHANGE) + class MyToken(Token): def get_category(self): @@ -102,8 +105,7 @@ def test_multiple_simple_words(btype): TokenAssignment(penalty=penalty, name=TokenRange(1, 3), address=[TokenRange(0, 1)]), TokenAssignment(penalty=penalty, name=TokenRange(2, 3), - address=[TokenRange(0, 2)]) - ) + address=[TokenRange(0, 2)])) def test_multiple_words_respect_phrase_break(): @@ -156,6 +158,7 @@ def test_housenumber_and_postcode(): address=[TokenRange(0, 1), TokenRange(2, 3)], postcode=TokenRange(3, 4))) + def test_postcode_and_housenumber(): q = make_query((qmod.BREAK_START, qmod.PHRASE_ANY, [(1, qmod.TOKEN_PARTIAL)]), (qmod.BREAK_WORD, qmod.PHRASE_ANY, [(2, qmod.TOKEN_POSTCODE)]), @@ -211,11 +214,11 @@ def test_housenumber_many_phrases(): check_assignments(yield_token_assignments(q), TokenAssignment(penalty=0.1, name=TokenRange(4, 5), - housenumber=TokenRange(3, 4),\ + housenumber=TokenRange(3, 4), address=[TokenRange(0, 1), TokenRange(1, 2), TokenRange(2, 3)]), TokenAssignment(penalty=0.1, - housenumber=TokenRange(3, 4),\ + housenumber=TokenRange(3, 4), address=[TokenRange(0, 1), TokenRange(1, 2), TokenRange(2, 3), TokenRange(4, 5)])) @@ -299,7 +302,6 @@ def test_qualifier_at_beginning(): (qmod.BREAK_WORD, qmod.PHRASE_ANY, [(2, qmod.TOKEN_PARTIAL)]), (qmod.BREAK_WORD, qmod.PHRASE_ANY, [(3, qmod.TOKEN_PARTIAL)])) - check_assignments(yield_token_assignments(q), TokenAssignment(penalty=0.1, name=TokenRange(1, 3), qualifier=TokenRange(0, 1)), @@ -315,7 +317,6 @@ def test_qualifier_after_name(): (qmod.BREAK_WORD, qmod.PHRASE_ANY, [(4, qmod.TOKEN_PARTIAL)]), (qmod.BREAK_WORD, qmod.PHRASE_ANY, [(5, qmod.TOKEN_PARTIAL)])) - check_assignments(yield_token_assignments(q), TokenAssignment(penalty=0.2, name=TokenRange(0, 2), qualifier=TokenRange(2, 3), @@ -349,4 +350,3 @@ def test_qualifier_in_middle_of_phrase(): (qmod.BREAK_PHRASE, qmod.PHRASE_ANY, [(5, qmod.TOKEN_PARTIAL)])) check_assignments(yield_token_assignments(q)) - diff --git a/test/python/api/test_api_connection.py b/test/python/api/test_api_connection.py index f62b0d9e..9b29411a 100644 --- a/test/python/api/test_api_connection.py +++ b/test/python/api/test_api_connection.py @@ -2,12 +2,11 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for enhanced connection class for API functions. """ -from pathlib import Path import pytest import sqlalchemy as sa @@ -76,7 +75,7 @@ async def test_get_db_property_existing(api): @pytest.mark.asyncio -async def test_get_db_property_existing(api): +async def test_get_db_property_bad_name(api): async with api.begin() as conn: with pytest.raises(ValueError): await conn.get_db_property('dfkgjd.rijg') diff --git a/test/python/api/test_api_deletable_v1.py b/test/python/api/test_api_deletable_v1.py index 9e113886..8ea4c9cd 100644 --- a/test/python/api/test_api_deletable_v1.py +++ b/test/python/api/test_api_deletable_v1.py @@ -2,20 +2,20 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for the deletable v1 API call. """ import json -from pathlib import Path import pytest -from fake_adaptor import FakeAdaptor, FakeError, FakeResponse +from fake_adaptor import FakeAdaptor import nominatim_api.v1.server_glue as glue + class TestDeletableEndPoint: @pytest.fixture(autouse=True) @@ -25,14 +25,13 @@ class TestDeletableEndPoint: content=[(345, 'N', 'boundary', 'administrative'), (781, 'R', 'landuse', 'wood'), (781, 'R', 'landcover', 'grass')]) - table_factory('placex', - definition="""place_id bigint, osm_id bigint, osm_type char(1), - class text, type text, name HSTORE, country_code char(2)""", - content=[(1, 345, 'N', 'boundary', 'administrative', {'old_name': 'Former'}, 'ab'), - (2, 781, 'R', 'landuse', 'wood', {'name': 'Wood'}, 'cd'), - (3, 781, 'R', 'landcover', 'grass', None, 'cd')]) - - + table_factory( + 'placex', + definition="""place_id bigint, osm_id bigint, osm_type char(1), + class text, type text, name HSTORE, country_code char(2)""", + content=[(1, 345, 'N', 'boundary', 'administrative', {'old_name': 'Former'}, 'ab'), + (2, 781, 'R', 'landuse', 'wood', {'name': 'Wood'}, 'cd'), + (3, 781, 'R', 'landcover', 'grass', None, 'cd')]) @pytest.mark.asyncio async def test_deletable(self, api): diff --git a/test/python/api/test_api_details.py b/test/python/api/test_api_details.py index 7f405728..4f6dd92b 100644 --- a/test/python/api/test_api_details.py +++ b/test/python/api/test_api_details.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for details API call. @@ -13,23 +13,24 @@ import pytest import nominatim_api as napi + @pytest.mark.parametrize('idobj', (napi.PlaceID(332), napi.OsmID('W', 4), napi.OsmID('W', 4, 'highway'))) def test_lookup_in_placex(apiobj, frontend, idobj): import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0) apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', - name={'name': 'Road'}, address={'city': 'Barrow'}, - extratags={'surface': 'paved'}, - parent_place_id=34, linked_place_id=55, - admin_level=15, country_code='gb', - housenumber='4', - postcode='34425', wikipedia='en:Faa', - rank_search=27, rank_address=26, - importance=0.01, - centroid=(23, 34), - indexed_date=import_date, - geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') + class_='highway', type='residential', + name={'name': 'Road'}, address={'city': 'Barrow'}, + extratags={'surface': 'paved'}, + parent_place_id=34, linked_place_id=55, + admin_level=15, country_code='gb', + housenumber='4', + postcode='34425', wikipedia='en:Faa', + rank_search=27, rank_address=26, + importance=0.01, + centroid=(23, 34), + indexed_date=import_date, + geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') api = frontend(apiobj, options={'details'}) result = api.details(idobj) @@ -73,12 +74,12 @@ def test_lookup_in_placex(apiobj, frontend, idobj): def test_lookup_in_placex_minimal_info(apiobj, frontend): import_date = dt.datetime(2022, 12, 7, 14, 14, 46, 0) apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', - admin_level=15, - rank_search=27, rank_address=26, - centroid=(23, 34), - indexed_date=import_date, - geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') + class_='highway', type='residential', + admin_level=15, + rank_search=27, rank_address=26, + centroid=(23, 34), + indexed_date=import_date, + geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') api = frontend(apiobj, options={'details'}) result = api.details(napi.PlaceID(332)) @@ -131,9 +132,9 @@ def test_lookup_in_placex_with_geometry(apiobj, frontend): def test_lookup_placex_with_address_details(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', name='Street', - country_code='pl', - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', + rank_search=27, rank_address=26) apiobj.add_address_placex(332, fromarea=False, isaddress=False, distance=0.0034, place_id=1000, osm_type='N', osm_id=3333, @@ -178,9 +179,9 @@ def test_lookup_placex_with_address_details(apiobj, frontend): def test_lookup_place_with_linked_places_none_existing(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', name='Street', - country_code='pl', linked_place_id=45, - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', linked_place_id=45, + rank_search=27, rank_address=26) api = frontend(apiobj, options={'details'}) result = api.details(napi.PlaceID(332), linked_places=True) @@ -190,17 +191,17 @@ def test_lookup_place_with_linked_places_none_existing(apiobj, frontend): def test_lookup_place_with_linked_places_existing(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', name='Street', - country_code='pl', linked_place_id=45, - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', linked_place_id=45, + rank_search=27, rank_address=26) apiobj.add_placex(place_id=1001, osm_type='W', osm_id=5, - class_='highway', type='residential', name='Street', - country_code='pl', linked_place_id=332, - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', linked_place_id=332, + rank_search=27, rank_address=26) apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6, - class_='highway', type='residential', name='Street', - country_code='pl', linked_place_id=332, - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', linked_place_id=332, + rank_search=27, rank_address=26) api = frontend(apiobj, options={'details'}) result = api.details(napi.PlaceID(332), linked_places=True) @@ -221,9 +222,9 @@ def test_lookup_place_with_linked_places_existing(apiobj, frontend): def test_lookup_place_with_parented_places_not_existing(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', name='Street', - country_code='pl', parent_place_id=45, - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', parent_place_id=45, + rank_search=27, rank_address=26) api = frontend(apiobj, options={'details'}) result = api.details(napi.PlaceID(332), parented_places=True) @@ -233,17 +234,17 @@ def test_lookup_place_with_parented_places_not_existing(apiobj, frontend): def test_lookup_place_with_parented_places_existing(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', name='Street', - country_code='pl', parent_place_id=45, - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', parent_place_id=45, + rank_search=27, rank_address=26) apiobj.add_placex(place_id=1001, osm_type='N', osm_id=5, - class_='place', type='house', housenumber='23', - country_code='pl', parent_place_id=332, - rank_search=30, rank_address=30) + class_='place', type='house', housenumber='23', + country_code='pl', parent_place_id=332, + rank_search=30, rank_address=30) apiobj.add_placex(place_id=1002, osm_type='W', osm_id=6, - class_='highway', type='residential', name='Street', - country_code='pl', parent_place_id=332, - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', parent_place_id=332, + rank_search=27, rank_address=26) api = frontend(apiobj, options={'details'}) result = api.details(napi.PlaceID(332), parented_places=True) @@ -332,9 +333,9 @@ def test_lookup_osmline_with_address_details(apiobj, frontend): startnumber=2, endnumber=4, step=1, parent_place_id=332) apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', name='Street', - country_code='pl', - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', + rank_search=27, rank_address=26) apiobj.add_address_placex(332, fromarea=False, isaddress=False, distance=0.0034, place_id=1000, osm_type='N', osm_id=3333, @@ -432,9 +433,9 @@ def test_lookup_tiger_with_address_details(apiobj, frontend): startnumber=2, endnumber=4, step=1, parent_place_id=332) apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', name='Street', - country_code='us', - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='us', + rank_search=27, rank_address=26) apiobj.add_address_placex(332, fromarea=False, isaddress=False, distance=0.0034, place_id=1000, osm_type='N', osm_id=3333, @@ -571,6 +572,7 @@ def test_lookup_postcode_with_address_details(apiobj, frontend): rank_address=4, distance=0.0) ] + @pytest.mark.parametrize('objid', [napi.PlaceID(1736), napi.OsmID('W', 55), napi.OsmID('N', 55, 'amenity')]) @@ -583,8 +585,8 @@ def test_lookup_missing_object(apiobj, frontend, objid): @pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML, - napi.GeometryFormat.SVG, - napi.GeometryFormat.TEXT)) + napi.GeometryFormat.SVG, + napi.GeometryFormat.TEXT)) def test_lookup_unsupported_geometry(apiobj, frontend, gtype): apiobj.add_placex(place_id=332) diff --git a/test/python/api/test_api_lookup.py b/test/python/api/test_api_lookup.py index 4281cd6c..a2660f51 100644 --- a/test/python/api/test_api_lookup.py +++ b/test/python/api/test_api_lookup.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for lookup API call. @@ -13,6 +13,7 @@ import pytest import nominatim_api as napi + def test_lookup_empty_list(apiobj, frontend): api = frontend(apiobj, options={'details'}) assert api.lookup([]) == [] @@ -28,17 +29,17 @@ def test_lookup_non_existing(apiobj, frontend): napi.OsmID('W', 4, 'highway'))) def test_lookup_single_placex(apiobj, frontend, idobj): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', - name={'name': 'Road'}, address={'city': 'Barrow'}, - extratags={'surface': 'paved'}, - parent_place_id=34, linked_place_id=55, - admin_level=15, country_code='gb', - housenumber='4', - postcode='34425', wikipedia='en:Faa', - rank_search=27, rank_address=26, - importance=0.01, - centroid=(23, 34), - geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') + class_='highway', type='residential', + name={'name': 'Road'}, address={'city': 'Barrow'}, + extratags={'surface': 'paved'}, + parent_place_id=34, linked_place_id=55, + admin_level=15, country_code='gb', + housenumber='4', + postcode='34425', wikipedia='en:Faa', + rank_search=27, rank_address=26, + importance=0.01, + centroid=(23, 34), + geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') api = frontend(apiobj, options={'details'}) result = api.lookup([idobj]) @@ -79,17 +80,17 @@ def test_lookup_single_placex(apiobj, frontend, idobj): def test_lookup_multiple_places(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', - name={'name': 'Road'}, address={'city': 'Barrow'}, - extratags={'surface': 'paved'}, - parent_place_id=34, linked_place_id=55, - admin_level=15, country_code='gb', - housenumber='4', - postcode='34425', wikipedia='en:Faa', - rank_search=27, rank_address=26, - importance=0.01, - centroid=(23, 34), - geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') + class_='highway', type='residential', + name={'name': 'Road'}, address={'city': 'Barrow'}, + extratags={'surface': 'paved'}, + parent_place_id=34, linked_place_id=55, + admin_level=15, country_code='gb', + housenumber='4', + postcode='34425', wikipedia='en:Faa', + rank_search=27, rank_address=26, + importance=0.01, + centroid=(23, 34), + geometry='LINESTRING(23 34, 23.1 34, 23.1 34.1, 23 34)') apiobj.add_osmline(place_id=4924, osm_id=9928, parent_place_id=12, startnumber=1, endnumber=4, step=1, @@ -97,7 +98,6 @@ def test_lookup_multiple_places(apiobj, frontend): address={'city': 'Big'}, geometry='LINESTRING(23 34, 23 35)') - api = frontend(apiobj, options={'details'}) result = api.lookup((napi.OsmID('W', 1), napi.OsmID('W', 4), @@ -111,17 +111,17 @@ def test_lookup_multiple_places(apiobj, frontend): @pytest.mark.parametrize('gtype', list(napi.GeometryFormat)) def test_simple_place_with_geometry(apiobj, frontend, gtype): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', - name={'name': 'Road'}, address={'city': 'Barrow'}, - extratags={'surface': 'paved'}, - parent_place_id=34, linked_place_id=55, - admin_level=15, country_code='gb', - housenumber='4', - postcode='34425', wikipedia='en:Faa', - rank_search=27, rank_address=26, - importance=0.01, - centroid=(23, 34), - geometry='POLYGON((23 34, 23.1 34, 23.1 34.1, 23 34))') + class_='highway', type='residential', + name={'name': 'Road'}, address={'city': 'Barrow'}, + extratags={'surface': 'paved'}, + parent_place_id=34, linked_place_id=55, + admin_level=15, country_code='gb', + housenumber='4', + postcode='34425', wikipedia='en:Faa', + rank_search=27, rank_address=26, + importance=0.01, + centroid=(23, 34), + geometry='POLYGON((23 34, 23.1 34, 23.1 34.1, 23 34))') api = frontend(apiobj, options={'details'}) result = api.lookup([napi.OsmID('W', 4)], geometry_output=gtype) @@ -137,17 +137,17 @@ def test_simple_place_with_geometry(apiobj, frontend, gtype): def test_simple_place_with_geometry_simplified(apiobj, frontend): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', - name={'name': 'Road'}, address={'city': 'Barrow'}, - extratags={'surface': 'paved'}, - parent_place_id=34, linked_place_id=55, - admin_level=15, country_code='gb', - housenumber='4', - postcode='34425', wikipedia='en:Faa', - rank_search=27, rank_address=26, - importance=0.01, - centroid=(23, 34), - geometry='POLYGON((23 34, 22.999 34, 23.1 34, 23.1 34.1, 23 34))') + class_='highway', type='residential', + name={'name': 'Road'}, address={'city': 'Barrow'}, + extratags={'surface': 'paved'}, + parent_place_id=34, linked_place_id=55, + admin_level=15, country_code='gb', + housenumber='4', + postcode='34425', wikipedia='en:Faa', + rank_search=27, rank_address=26, + importance=0.01, + centroid=(23, 34), + geometry='POLYGON((23 34, 22.999 34, 23.1 34, 23.1 34.1, 23 34))') api = frontend(apiobj, options={'details'}) result = api.lookup([napi.OsmID('W', 4)], @@ -159,5 +159,5 @@ def test_simple_place_with_geometry_simplified(apiobj, frontend): geom = json.loads(result[0].geometry['geojson']) - assert geom['type'] == 'Polygon' + assert geom['type'] == 'Polygon' assert geom['coordinates'] == [[[23, 34], [23.1, 34], [23.1, 34.1], [23, 34]]] diff --git a/test/python/api/test_api_polygons_v1.py b/test/python/api/test_api_polygons_v1.py index ac2b4cb9..e4700a95 100644 --- a/test/python/api/test_api_polygons_v1.py +++ b/test/python/api/test_api_polygons_v1.py @@ -2,21 +2,21 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for the deletable v1 API call. """ import json import datetime as dt -from pathlib import Path import pytest -from fake_adaptor import FakeAdaptor, FakeError, FakeResponse +from fake_adaptor import FakeAdaptor import nominatim_api.v1.server_glue as glue + class TestPolygonsEndPoint: @pytest.fixture(autouse=True) @@ -35,13 +35,12 @@ class TestPolygonsEndPoint: errormessage text, prevgeometry geometry(Geometry,4326), newgeometry geometry(Geometry,4326)""", - content=[(345, 'N', 'boundary', 'administrative', - {'name': 'Foo'}, 'xx', self.recent, - 'some text', None, None), - (781, 'R', 'landuse', 'wood', - None, 'ds', self.now, - 'Area reduced by lots', None, None)]) - + content=[(345, 'N', 'boundary', 'administrative', + {'name': 'Foo'}, 'xx', self.recent, + 'some text', None, None), + (781, 'R', 'landuse', 'wood', + None, 'ds', self.now, + 'Area reduced by lots', None, None)]) @pytest.mark.asyncio async def test_polygons_simple(self, api): @@ -63,7 +62,6 @@ class TestPolygonsEndPoint: 'errormessage': 'Area reduced by lots', 'updated': self.now.isoformat(sep=' ', timespec='seconds')}] - @pytest.mark.asyncio async def test_polygons_days(self, api): a = FakeAdaptor() @@ -74,7 +72,6 @@ class TestPolygonsEndPoint: assert [r['osm_id'] for r in results] == [781] - @pytest.mark.asyncio async def test_polygons_class(self, api): a = FakeAdaptor() @@ -85,8 +82,6 @@ class TestPolygonsEndPoint: assert [r['osm_id'] for r in results] == [781] - - @pytest.mark.asyncio async def test_polygons_reduced(self, api): a = FakeAdaptor() diff --git a/test/python/api/test_api_reverse.py b/test/python/api/test_api_reverse.py index ff7f402b..91074ecb 100644 --- a/test/python/api/test_api_reverse.py +++ b/test/python/api/test_api_reverse.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for reverse API call. @@ -18,6 +18,7 @@ import nominatim_api as napi API_OPTIONS = {'reverse'} + def test_reverse_rank_30(apiobj, frontend): apiobj.add_placex(place_id=223, class_='place', type='house', housenumber='1', @@ -35,7 +36,7 @@ def test_reverse_rank_30(apiobj, frontend): def test_reverse_street(apiobj, frontend, country): apiobj.add_placex(place_id=990, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'My Street'}, + name={'name': 'My Street'}, centroid=(10.0, 10.0), country_code=country, geometry='LINESTRING(9.995 10, 10.005 10)') @@ -57,16 +58,17 @@ def test_reverse_ignore_unindexed(apiobj, frontend): assert result is None -@pytest.mark.parametrize('y,layer,place_id', [(0.7, napi.DataLayer.ADDRESS, 223), - (0.70001, napi.DataLayer.POI, 224), - (0.7, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 224), - (0.70001, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 223), - (0.7, napi.DataLayer.MANMADE, 225), - (0.7, napi.DataLayer.RAILWAY, 226), - (0.7, napi.DataLayer.NATURAL, 227), - (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225), - (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225), - (5, napi.DataLayer.ADDRESS, 229)]) +@pytest.mark.parametrize('y,layer,place_id', + [(0.7, napi.DataLayer.ADDRESS, 223), + (0.70001, napi.DataLayer.POI, 224), + (0.7, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 224), + (0.70001, napi.DataLayer.ADDRESS | napi.DataLayer.POI, 223), + (0.7, napi.DataLayer.MANMADE, 225), + (0.7, napi.DataLayer.RAILWAY, 226), + (0.7, napi.DataLayer.NATURAL, 227), + (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, 225), + (0.70003, napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, 225), + (5, napi.DataLayer.ADDRESS, 229)]) def test_reverse_rank_30_layers(apiobj, frontend, y, layer, place_id): apiobj.add_placex(place_id=223, osm_type='N', class_='place', type='house', housenumber='1', @@ -108,14 +110,14 @@ def test_reverse_poi_layer_with_no_pois(apiobj, frontend): api = frontend(apiobj, options=API_OPTIONS) assert api.reverse((1.3, 0.70001), max_rank=29, - layers=napi.DataLayer.POI) is None + layers=napi.DataLayer.POI) is None @pytest.mark.parametrize('with_geom', [True, False]) def test_reverse_housenumber_on_street(apiobj, frontend, with_geom): apiobj.add_placex(place_id=990, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'My Street'}, + name={'name': 'My Street'}, centroid=(10.0, 10.0), geometry='LINESTRING(9.995 10, 10.005 10)') apiobj.add_placex(place_id=991, class_='place', type='house', @@ -125,7 +127,7 @@ def test_reverse_housenumber_on_street(apiobj, frontend, with_geom): centroid=(10.0, 10.00001)) apiobj.add_placex(place_id=1990, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'Other Street'}, + name={'name': 'Other Street'}, centroid=(10.0, 1.0), geometry='LINESTRING(9.995 1, 10.005 1)') apiobj.add_placex(place_id=1991, class_='place', type='house', @@ -147,7 +149,7 @@ def test_reverse_housenumber_on_street(apiobj, frontend, with_geom): def test_reverse_housenumber_interpolation(apiobj, frontend, with_geom): apiobj.add_placex(place_id=990, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'My Street'}, + name={'name': 'My Street'}, centroid=(10.0, 10.0), geometry='LINESTRING(9.995 10, 10.005 10)') apiobj.add_placex(place_id=991, class_='place', type='house', @@ -162,7 +164,7 @@ def test_reverse_housenumber_interpolation(apiobj, frontend, with_geom): geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)') apiobj.add_placex(place_id=1990, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'Other Street'}, + name={'name': 'Other Street'}, centroid=(10.0, 20.0), geometry='LINESTRING(9.995 20, 10.005 20)') apiobj.add_osmline(place_id=1992, @@ -181,7 +183,7 @@ def test_reverse_housenumber_interpolation(apiobj, frontend, with_geom): def test_reverse_housenumber_point_interpolation(apiobj, frontend): apiobj.add_placex(place_id=990, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'My Street'}, + name={'name': 'My Street'}, centroid=(10.0, 10.0), geometry='LINESTRING(9.995 10, 10.005 10)') apiobj.add_osmline(place_id=992, @@ -199,7 +201,7 @@ def test_reverse_housenumber_point_interpolation(apiobj, frontend): def test_reverse_tiger_number(apiobj, frontend): apiobj.add_placex(place_id=990, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'My Street'}, + name={'name': 'My Street'}, centroid=(10.0, 10.0), country_code='us', geometry='LINESTRING(9.995 10, 10.005 10)') @@ -217,7 +219,7 @@ def test_reverse_tiger_number(apiobj, frontend): def test_reverse_point_tiger(apiobj, frontend): apiobj.add_placex(place_id=990, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'My Street'}, + name={'name': 'My Street'}, centroid=(10.0, 10.0), country_code='us', geometry='LINESTRING(9.995 10, 10.005 10)') @@ -393,14 +395,15 @@ def test_reverse_interpolation_geometry(apiobj, frontend): geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)') api = frontend(apiobj, options=API_OPTIONS) - assert api.reverse((10.0, 10.0), geometry_output=napi.GeometryFormat.TEXT)\ - .geometry['text'] == 'POINT(10 10.00001)' + result = api.reverse((10.0, 10.0), geometry_output=napi.GeometryFormat.TEXT) + + assert result.geometry['text'] == 'POINT(10 10.00001)' def test_reverse_tiger_geometry(apiobj, frontend): apiobj.add_placex(place_id=990, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'My Street'}, + name={'name': 'My Street'}, centroid=(10.0, 10.0), country_code='us', geometry='LINESTRING(9.995 10, 10.005 10)') @@ -411,7 +414,7 @@ def test_reverse_tiger_geometry(apiobj, frontend): geometry='LINESTRING(9.995 10.00001, 10.005 10.00001)') apiobj.add_placex(place_id=1000, class_='highway', type='service', rank_search=27, rank_address=27, - name = {'name': 'My Street'}, + name={'name': 'My Street'}, centroid=(11.0, 11.0), country_code='us', geometry='LINESTRING(10.995 11, 11.005 11)') @@ -426,8 +429,9 @@ def test_reverse_tiger_geometry(apiobj, frontend): params = {'geometry_output': napi.GeometryFormat.GEOJSON} output = api.reverse((10.0, 10.0), **params) - assert json.loads(output.geometry['geojson']) == {'coordinates': [10, 10.00001], 'type': 'Point'} + assert json.loads(output.geometry['geojson']) \ + == {'coordinates': [10, 10.00001], 'type': 'Point'} output = api.reverse((11.0, 11.0), **params) - assert json.loads(output.geometry['geojson']) == {'coordinates': [11, 11.00001], 'type': 'Point'} - + assert json.loads(output.geometry['geojson']) \ + == {'coordinates': [11, 11.00001], 'type': 'Point'} diff --git a/test/python/api/test_api_search.py b/test/python/api/test_api_search.py index 54138e24..59a83aa9 100644 --- a/test/python/api/test_api_search.py +++ b/test/python/api/test_api_search.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for search API calls. @@ -10,17 +10,13 @@ Tests for search API calls. These tests make sure that all Python code is correct and executable. Functional tests can be found in the BDD test suite. """ -import json - import pytest -import sqlalchemy as sa - -import nominatim_api as napi import nominatim_api.logging as loglib API_OPTIONS = {'search'} + @pytest.fixture(autouse=True) def setup_icu_tokenizer(apiobj): """ Setup the properties needed for using the ICU tokenizer. @@ -28,8 +24,9 @@ def setup_icu_tokenizer(apiobj): apiobj.add_data('properties', [{'property': 'tokenizer', 'value': 'icu'}, {'property': 'tokenizer_import_normalisation', 'value': ':: lower();'}, - {'property': 'tokenizer_import_transliteration', 'value': "'1' > '/1/'; 'ä' > 'ä '"}, - ]) + {'property': 'tokenizer_import_transliteration', + 'value': "'1' > '/1/'; 'ä' > 'ä '"}, + ]) def test_search_no_content(apiobj, frontend): @@ -64,7 +61,7 @@ def test_search_with_debug(apiobj, frontend, logtype): api = frontend(apiobj, options=API_OPTIONS) loglib.set_log_output(logtype) - results = api.search('TEST') + api.search('TEST') assert loglib.get_and_disable() diff --git a/test/python/api/test_api_status.py b/test/python/api/test_api_status.py index 9341b527..a80c8710 100644 --- a/test/python/api/test_api_status.py +++ b/test/python/api/test_api_status.py @@ -2,18 +2,17 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for the status API call. """ import datetime as dt -import pytest -from nominatim_db.version import NominatimVersion from nominatim_api.version import NOMINATIM_API_VERSION import nominatim_api as napi + def test_status_no_extra_info(apiobj, frontend): api = frontend(apiobj) result = api.status() diff --git a/test/python/api/test_api_types.py b/test/python/api/test_api_types.py index fbb9b682..898b884d 100644 --- a/test/python/api/test_api_types.py +++ b/test/python/api/test_api_types.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for loading of parameter dataclasses. @@ -12,6 +12,7 @@ import pytest from nominatim_api.errors import UsageError import nominatim_api.types as typ + def test_no_params_defaults(): params = typ.LookupDetails.from_kwargs({}) @@ -24,7 +25,7 @@ def test_no_params_defaults(): ('geometry_simplification', 'NaN')]) def test_bad_format_reverse(k, v): with pytest.raises(UsageError): - params = typ.ReverseDetails.from_kwargs({k: v}) + typ.ReverseDetails.from_kwargs({k: v}) @pytest.mark.parametrize('rin,rout', [(-23, 0), (0, 0), (1, 1), diff --git a/test/python/api/test_export.py b/test/python/api/test_export.py index b0da52ce..c94cb7fb 100644 --- a/test/python/api/test_export.py +++ b/test/python/api/test_export.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for export CLI function. @@ -11,12 +11,13 @@ import pytest import nominatim_db.cli + @pytest.fixture def run_export(tmp_path, capsys): def _exec(args): + cli_args = ['export', '--project-dir', str(tmp_path)] + args assert 0 == nominatim_db.cli.nominatim(osm2pgsql_path='OSM2PGSQL NOT AVAILABLE', - cli_args=['export', '--project-dir', str(tmp_path)] - + args) + cli_args=cli_args) return capsys.readouterr().out.split('\r\n') return _exec @@ -25,9 +26,9 @@ def run_export(tmp_path, capsys): @pytest.fixture(autouse=True) def setup_database_with_context(apiobj): apiobj.add_placex(place_id=332, osm_type='W', osm_id=4, - class_='highway', type='residential', name='Street', - country_code='pl', postcode='55674', - rank_search=27, rank_address=26) + class_='highway', type='residential', name='Street', + country_code='pl', postcode='55674', + rank_search=27, rank_address=26) apiobj.add_address_placex(332, fromarea=False, isaddress=False, distance=0.0034, place_id=1000, osm_type='N', osm_id=3333, diff --git a/test/python/api/test_helpers_v1.py b/test/python/api/test_helpers_v1.py index 3a6a9a0b..10f0921b 100644 --- a/test/python/api/test_helpers_v1.py +++ b/test/python/api/test_helpers_v1.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for the helper functions for v1 API. @@ -11,6 +11,7 @@ import pytest import nominatim_api.v1.helpers as helper + @pytest.mark.parametrize('inp', ['', 'abc', '12 23', @@ -35,40 +36,42 @@ def test_extract_coords_with_text_before(): def test_extract_coords_with_text_after(): assert ('abc', 12.456, -78.90) == helper.extract_coords_from_query('-78.90, 12.456 abc') + @pytest.mark.parametrize('inp', [' [12.456,-78.90] ', ' 12.456,-78.90 ']) def test_extract_coords_with_spaces(inp): assert ('', -78.90, 12.456) == helper.extract_coords_from_query(inp) + @pytest.mark.parametrize('inp', ['40 26.767 N 79 58.933 W', - '40° 26.767′ N 79° 58.933′ W', - "40° 26.767' N 79° 58.933' W", - "40° 26.767'\n" - " N 79° 58.933' W", - 'N 40 26.767, W 79 58.933', - 'N 40°26.767′, W 79°58.933′', - ' N 40°26.767′, W 79°58.933′', - "N 40°26.767', W 79°58.933'", - - '40 26 46 N 79 58 56 W', - '40° 26′ 46″ N 79° 58′ 56″ W', - '40° 26′ 46.00″ N 79° 58′ 56.00″ W', - '40°26′46″N 79°58′56″W', - 'N 40 26 46 W 79 58 56', - 'N 40° 26′ 46″, W 79° 58′ 56″', - 'N 40° 26\' 46", W 79° 58\' 56"', - 'N 40° 26\' 46", W 79° 58\' 56"', - - '40.446 -79.982', - '40.446,-79.982', - '40.446° N 79.982° W', - 'N 40.446° W 79.982°', - - '[40.446 -79.982]', - '[40.446, -79.982]', - ' 40.446 , -79.982 ', - ' 40.446 , -79.982 ', - ' 40.446 , -79.982 ', - ' 40.446 , -79.982 ']) + '40° 26.767′ N 79° 58.933′ W', + "40° 26.767' N 79° 58.933' W", + "40° 26.767'\n" + " N 79° 58.933' W", + 'N 40 26.767, W 79 58.933', + 'N 40°26.767′, W 79°58.933′', + ' N 40°26.767′, W 79°58.933′', + "N 40°26.767', W 79°58.933'", + + '40 26 46 N 79 58 56 W', + '40° 26′ 46″ N 79° 58′ 56″ W', + '40° 26′ 46.00″ N 79° 58′ 56.00″ W', + '40°26′46″N 79°58′56″W', + 'N 40 26 46 W 79 58 56', + 'N 40° 26′ 46″, W 79° 58′ 56″', + 'N 40° 26\' 46", W 79° 58\' 56"', + 'N 40° 26\' 46", W 79° 58\' 56"', + + '40.446 -79.982', + '40.446,-79.982', + '40.446° N 79.982° W', + 'N 40.446° W 79.982°', + + '[40.446 -79.982]', + '[40.446, -79.982]', + ' 40.446 , -79.982 ', + ' 40.446 , -79.982 ', + ' 40.446 , -79.982 ', + ' 40.446 , -79.982 ']) def test_extract_coords_formats(inp): query, x, y = helper.extract_coords_from_query(inp) @@ -108,9 +111,11 @@ def test_extract_category_good(inp): assert cls == 'shop' assert typ == 'fish' + def test_extract_category_only(): assert helper.extract_category_from_query('[shop=market]') == ('', 'shop', 'market') + @pytest.mark.parametrize('inp', ['house []', 'nothing', '[352]']) -def test_extract_category_no_match(inp): +def test_extract_category_no_match(inp): assert helper.extract_category_from_query(inp) == (inp, None, None) diff --git a/test/python/api/test_localization.py b/test/python/api/test_localization.py index 21fa72c8..0a30cdc1 100644 --- a/test/python/api/test_localization.py +++ b/test/python/api/test_localization.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Test functions for adapting results to the user's locale. @@ -11,34 +11,36 @@ import pytest from nominatim_api import Locales + def test_display_name_empty_names(): - l = Locales(['en', 'de']) + loc = Locales(['en', 'de']) + + assert loc.display_name(None) == '' + assert loc.display_name({}) == '' - assert l.display_name(None) == '' - assert l.display_name({}) == '' def test_display_name_none_localized(): - l = Locales() + loc = Locales() - assert l.display_name({}) == '' - assert l.display_name({'name:de': 'DE', 'name': 'ALL'}) == 'ALL' - assert l.display_name({'ref': '34', 'name:de': 'DE'}) == '34' + assert loc.display_name({}) == '' + assert loc.display_name({'name:de': 'DE', 'name': 'ALL'}) == 'ALL' + assert loc.display_name({'ref': '34', 'name:de': 'DE'}) == '34' def test_display_name_localized(): - l = Locales(['en', 'de']) + loc = Locales(['en', 'de']) - assert l.display_name({}) == '' - assert l.display_name({'name:de': 'DE', 'name': 'ALL'}) == 'DE' - assert l.display_name({'ref': '34', 'name:de': 'DE'}) == 'DE' + assert loc.display_name({}) == '' + assert loc.display_name({'name:de': 'DE', 'name': 'ALL'}) == 'DE' + assert loc.display_name({'ref': '34', 'name:de': 'DE'}) == 'DE' def test_display_name_preference(): - l = Locales(['en', 'de']) + loc = Locales(['en', 'de']) - assert l.display_name({}) == '' - assert l.display_name({'name:de': 'DE', 'name:en': 'EN'}) == 'EN' - assert l.display_name({'official_name:en': 'EN', 'name:de': 'DE'}) == 'DE' + assert loc.display_name({}) == '' + assert loc.display_name({'name:de': 'DE', 'name:en': 'EN'}) == 'EN' + assert loc.display_name({'official_name:en': 'EN', 'name:de': 'DE'}) == 'DE' @pytest.mark.parametrize('langstr,langlist', diff --git a/test/python/api/test_result_formatting_v1.py b/test/python/api/test_result_formatting_v1.py index aaecab45..406c7654 100644 --- a/test/python/api/test_result_formatting_v1.py +++ b/test/python/api/test_result_formatting_v1.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for formatting results for the V1 API. @@ -22,6 +22,7 @@ STATUS_FORMATS = {'text', 'json'} # StatusResult + def test_status_format_list(): assert set(v1_format.list_formats(napi.StatusResult)) == STATUS_FORMATS @@ -36,11 +37,13 @@ def test_status_unsupported(): def test_status_format_text(): - assert v1_format.format_result(napi.StatusResult(0, 'message here'), 'text', {}) == 'OK' + assert v1_format.format_result(napi.StatusResult(0, 'message here'), 'text', {}) \ + == 'OK' -def test_status_format_text(): - assert v1_format.format_result(napi.StatusResult(500, 'message here'), 'text', {}) == 'ERROR: message here' +def test_status_format_error_text(): + assert v1_format.format_result(napi.StatusResult(500, 'message here'), 'text', {}) \ + == 'ERROR: message here' def test_status_format_json_minimal(): @@ -48,8 +51,9 @@ def test_status_format_json_minimal(): result = v1_format.format_result(status, 'json', {}) - assert result == \ - f'{{"status":700,"message":"Bad format.","software_version":"{napi.__version__}"}}' + assert json.loads(result) == {'status': 700, + 'message': 'Bad format.', + 'software_version': napi.__version__} def test_status_format_json_full(): @@ -59,8 +63,11 @@ def test_status_format_json_full(): result = v1_format.format_result(status, 'json', {}) - assert result == \ - f'{{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"{napi.__version__}","database_version":"5.6"}}' + assert json.loads(result) == {'status': 0, + 'message': 'OK', + 'data_updated': '2010-02-07T20:20:03+00:00', + 'software_version': napi.__version__, + 'database_version': '5.6'} # DetailedResult @@ -86,7 +93,7 @@ def test_search_details_minimal(): 'extratags': {}, 'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]}, 'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]}, - } + } def test_search_details_full(): @@ -110,7 +117,7 @@ def test_search_details_full(): rank_search=28, importance=0.0443, country_code='ll', - indexed_date = import_date + indexed_date=import_date ) search.localize(napi.Locales()) @@ -140,7 +147,7 @@ def test_search_details_full(): 'isarea': False, 'centroid': {'type': 'Point', 'coordinates': [56.947, -87.44]}, 'geometry': {'type': 'Point', 'coordinates': [56.947, -87.44]}, - } + } @pytest.mark.parametrize('gtype,isarea', [('ST_Point', False), @@ -149,9 +156,9 @@ def test_search_details_full(): ('ST_MultiPolygon', True)]) def test_search_details_no_geometry(gtype, isarea): search = napi.DetailedResult(napi.SourceTable.PLACEX, - ('place', 'thing'), - napi.Point(1.0, 2.0), - geometry={'type': gtype}) + ('place', 'thing'), + napi.Point(1.0, 2.0), + geometry={'type': gtype}) result = v1_format.format_result(search, 'json', {}) js = json.loads(result) @@ -161,16 +168,17 @@ def test_search_details_no_geometry(gtype, isarea): def test_search_details_with_geometry(): - search = napi.DetailedResult(napi.SourceTable.PLACEX, - ('place', 'thing'), - napi.Point(1.0, 2.0), - geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'}) + search = napi.DetailedResult( + napi.SourceTable.PLACEX, + ('place', 'thing'), + napi.Point(1.0, 2.0), + geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'}) result = v1_format.format_result(search, 'json', {}) js = json.loads(result) assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]} - assert js['isarea'] == False + assert js['isarea'] is False def test_search_details_with_icon_available(): @@ -226,7 +234,7 @@ def test_search_details_with_address_minimal(): @pytest.mark.parametrize('field,outfield', [('address_rows', 'address'), ('linked_rows', 'linked_places'), ('parented_rows', 'hierarchy') - ]) + ]) def test_search_details_with_further_infos(field, outfield): search = napi.DetailedResult(napi.SourceTable.PLACEX, ('place', 'thing'), @@ -249,50 +257,49 @@ def test_search_details_with_further_infos(field, outfield): js = json.loads(result) assert js[outfield] == [{'localname': 'Trespass', - 'place_id': 3498, - 'osm_id': 442, - 'osm_type': 'R', - 'place_type': 'spec', - 'class': 'bnd', - 'type': 'note', - 'admin_level': 4, - 'rank_address': 10, - 'distance': 0.034, - 'isaddress': True}] + 'place_id': 3498, + 'osm_id': 442, + 'osm_type': 'R', + 'place_type': 'spec', + 'class': 'bnd', + 'type': 'note', + 'admin_level': 4, + 'rank_address': 10, + 'distance': 0.034, + 'isaddress': True}] def test_search_details_grouped_hierarchy(): search = napi.DetailedResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, 2.0), - parented_rows = - [napi.AddressLine(place_id=3498, - osm_object=('R', 442), - category=('bnd', 'note'), - names={'name': 'Trespass'}, - extratags={'access': 'no', - 'place_type': 'spec'}, - admin_level=4, - fromarea=True, - isaddress=True, - rank_address=10, - distance=0.034) - ]) + parented_rows=[napi.AddressLine( + place_id=3498, + osm_object=('R', 442), + category=('bnd', 'note'), + names={'name': 'Trespass'}, + extratags={'access': 'no', + 'place_type': 'spec'}, + admin_level=4, + fromarea=True, + isaddress=True, + rank_address=10, + distance=0.034)]) result = v1_format.format_result(search, 'json', {'group_hierarchy': True}) js = json.loads(result) assert js['hierarchy'] == {'note': [{'localname': 'Trespass', - 'place_id': 3498, - 'osm_id': 442, - 'osm_type': 'R', - 'place_type': 'spec', - 'class': 'bnd', - 'type': 'note', - 'admin_level': 4, - 'rank_address': 10, - 'distance': 0.034, - 'isaddress': True}]} + 'place_id': 3498, + 'osm_id': 442, + 'osm_type': 'R', + 'place_type': 'spec', + 'class': 'bnd', + 'type': 'note', + 'admin_level': 4, + 'rank_address': 10, + 'distance': 0.034, + 'isaddress': True}]} def test_search_details_keywords_name(): @@ -307,7 +314,7 @@ def test_search_details_keywords_name(): js = json.loads(result) assert js['keywords'] == {'name': [{'id': 23, 'token': 'foo'}, - {'id': 24, 'token': 'foo'}], + {'id': 24, 'token': 'foo'}], 'address': []} @@ -323,6 +330,5 @@ def test_search_details_keywords_address(): js = json.loads(result) assert js['keywords'] == {'address': [{'id': 23, 'token': 'foo'}, - {'id': 24, 'token': 'foo'}], + {'id': 24, 'token': 'foo'}], 'name': []} - diff --git a/test/python/api/test_result_formatting_v1_reverse.py b/test/python/api/test_result_formatting_v1_reverse.py index 2c036a65..902f0e79 100644 --- a/test/python/api/test_result_formatting_v1_reverse.py +++ b/test/python/api/test_result_formatting_v1_reverse.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for formatting reverse results for the V1 API. @@ -20,6 +20,7 @@ import nominatim_api as napi FORMATS = ['json', 'jsonv2', 'geojson', 'geocodejson', 'xml'] + @pytest.mark.parametrize('fmt', FORMATS) def test_format_reverse_minimal(fmt): reverse = napi.ReverseResult(napi.SourceTable.PLACEX, @@ -104,8 +105,7 @@ def test_format_reverse_with_address(fmt): reverse.localize(napi.Locales()) raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, - {'addressdetails': True}) - + {'addressdetails': True}) if fmt == 'xml': root = ET.fromstring(raw) @@ -168,7 +168,7 @@ def test_format_reverse_geocodejson_special_parts(): reverse.localize(napi.Locales()) raw = v1_format.format_result(napi.ReverseResults([reverse]), 'geocodejson', - {'addressdetails': True}) + {'addressdetails': True}) props = json.loads(raw)['features'][0]['properties']['geocoding'] assert props['housenumber'] == '1' @@ -184,8 +184,7 @@ def test_format_reverse_with_address_none(fmt): address_rows=napi.AddressLines()) raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, - {'addressdetails': True}) - + {'addressdetails': True}) if fmt == 'xml': root = ET.fromstring(raw) @@ -211,10 +210,10 @@ def test_format_reverse_with_extratags(fmt): reverse = napi.ReverseResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, 2.0), - extratags={'one': 'A', 'two':'B'}) + extratags={'one': 'A', 'two': 'B'}) raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, - {'extratags': True}) + {'extratags': True}) if fmt == 'xml': root = ET.fromstring(raw) @@ -226,7 +225,7 @@ def test_format_reverse_with_extratags(fmt): else: extra = result['extratags'] - assert extra == {'one': 'A', 'two':'B'} + assert extra == {'one': 'A', 'two': 'B'} @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml']) @@ -236,7 +235,7 @@ def test_format_reverse_with_extratags_none(fmt): napi.Point(1.0, 2.0)) raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, - {'extratags': True}) + {'extratags': True}) if fmt == 'xml': root = ET.fromstring(raw) @@ -256,10 +255,10 @@ def test_format_reverse_with_namedetails_with_name(fmt): reverse = napi.ReverseResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, 2.0), - names={'name': 'A', 'ref':'1'}) + names={'name': 'A', 'ref': '1'}) raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, - {'namedetails': True}) + {'namedetails': True}) if fmt == 'xml': root = ET.fromstring(raw) @@ -271,7 +270,7 @@ def test_format_reverse_with_namedetails_with_name(fmt): else: extra = result['namedetails'] - assert extra == {'name': 'A', 'ref':'1'} + assert extra == {'name': 'A', 'ref': '1'} @pytest.mark.parametrize('fmt', ['json', 'jsonv2', 'geojson', 'xml']) @@ -281,7 +280,7 @@ def test_format_reverse_with_namedetails_without_name(fmt): napi.Point(1.0, 2.0)) raw = v1_format.format_result(napi.ReverseResults([reverse]), fmt, - {'namedetails': True}) + {'namedetails': True}) if fmt == 'xml': root = ET.fromstring(raw) @@ -303,7 +302,7 @@ def test_search_details_with_icon_available(fmt): napi.Point(1.0, 2.0)) result = v1_format.format_result(napi.ReverseResults([reverse]), fmt, - {'icon_base_url': 'foo'}) + {'icon_base_url': 'foo'}) js = json.loads(result) @@ -317,7 +316,6 @@ def test_search_details_with_icon_not_available(fmt): napi.Point(1.0, 2.0)) result = v1_format.format_result(napi.ReverseResults([reverse]), fmt, - {'icon_base_url': 'foo'}) + {'icon_base_url': 'foo'}) assert 'icon' not in json.loads(result) - diff --git a/test/python/api/test_results.py b/test/python/api/test_results.py index f0bfa163..8e9fbf68 100644 --- a/test/python/api/test_results.py +++ b/test/python/api/test_results.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for result datatype helper functions. @@ -11,16 +11,15 @@ import struct from binascii import hexlify import pytest -import pytest_asyncio -import sqlalchemy as sa - from nominatim_api import SourceTable, DetailedResult, Point import nominatim_api.results as nresults + def mkpoint(x, y): return hexlify(struct.pack("=biidd", 1, 0x20000001, 4326, x, y)).decode('utf-8') + class FakeRow: def __init__(self, **kwargs): if 'parent_place_id' not in kwargs: @@ -39,6 +38,7 @@ def test_minimal_detailed_result(): assert res.lat == 0.5 assert res.calculated_importance() == pytest.approx(0.00001) + def test_detailed_result_custom_importance(): res = DetailedResult(SourceTable.PLACEX, ('amenity', 'post_box'), diff --git a/test/python/api/test_server_glue_v1.py b/test/python/api/test_server_glue_v1.py index 6ea790c0..8d9f0940 100644 --- a/test/python/api/test_server_glue_v1.py +++ b/test/python/api/test_server_glue_v1.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for the Python web frameworks adaptor, v1 API. @@ -121,7 +121,6 @@ class TestAdaptorRaiseError: return excinfo.value - def test_without_content_set(self): err = self.run_raise_error('TEST', 404) @@ -129,7 +128,6 @@ class TestAdaptorRaiseError: assert err.msg == 'ERROR 404: TEST' assert err.status == 404 - def test_json(self): self.adaptor.content_type = 'application/json; charset=utf-8' @@ -139,7 +137,6 @@ class TestAdaptorRaiseError: assert content['code'] == 501 assert content['message'] == 'TEST' - def test_xml(self): self.adaptor.content_type = 'text/xml; charset=utf-8' @@ -235,7 +232,6 @@ class TestStatusEndpoint: monkeypatch.setattr(napi.NominatimAPIAsync, 'status', _status) - @pytest.mark.asyncio async def test_status_without_params(self): a = FakeAdaptor() @@ -247,7 +243,6 @@ class TestStatusEndpoint: assert resp.status == 200 assert resp.content_type == 'text/plain; charset=utf-8' - @pytest.mark.asyncio async def test_status_with_error(self): a = FakeAdaptor() @@ -259,7 +254,6 @@ class TestStatusEndpoint: assert resp.status == 500 assert resp.content_type == 'text/plain; charset=utf-8' - @pytest.mark.asyncio async def test_status_json_with_error(self): a = FakeAdaptor(params={'format': 'json'}) @@ -271,7 +265,6 @@ class TestStatusEndpoint: assert resp.status == 200 assert resp.content_type == 'application/json; charset=utf-8' - @pytest.mark.asyncio async def test_status_bad_format(self): a = FakeAdaptor(params={'format': 'foo'}) @@ -298,7 +291,6 @@ class TestDetailsEndpoint: monkeypatch.setattr(napi.NominatimAPIAsync, 'details', _lookup) - @pytest.mark.asyncio async def test_details_no_params(self): a = FakeAdaptor() @@ -306,7 +298,6 @@ class TestDetailsEndpoint: with pytest.raises(FakeError, match='^400 -- .*Missing'): await glue.details_endpoint(napi.NominatimAPIAsync(), a) - @pytest.mark.asyncio async def test_details_by_place_id(self): a = FakeAdaptor(params={'place_id': '4573'}) @@ -315,7 +306,6 @@ class TestDetailsEndpoint: assert self.lookup_args[0].place_id == 4573 - @pytest.mark.asyncio async def test_details_by_osm_id(self): a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45'}) @@ -326,7 +316,6 @@ class TestDetailsEndpoint: assert self.lookup_args[0].osm_id == 45 assert self.lookup_args[0].osm_class is None - @pytest.mark.asyncio async def test_details_with_debugging(self): a = FakeAdaptor(params={'osmtype': 'N', 'osmid': '45', 'debug': '1'}) @@ -337,7 +326,6 @@ class TestDetailsEndpoint: assert resp.content_type == 'text/html; charset=utf-8' assert content.tag == 'html' - @pytest.mark.asyncio async def test_details_no_result(self): a = FakeAdaptor(params={'place_id': '4573'}) @@ -353,14 +341,14 @@ class TestReverseEndPoint: @pytest.fixture(autouse=True) def patch_reverse_func(self, monkeypatch): self.result = napi.ReverseResult(napi.SourceTable.PLACEX, - ('place', 'thing'), - napi.Point(1.0, 2.0)) + ('place', 'thing'), + napi.Point(1.0, 2.0)) + async def _reverse(*args, **kwargs): return self.result monkeypatch.setattr(napi.NominatimAPIAsync, 'reverse', _reverse) - @pytest.mark.asyncio @pytest.mark.parametrize('params', [{}, {'lat': '3.4'}, {'lon': '6.7'}]) async def test_reverse_no_params(self, params): @@ -371,19 +359,6 @@ class TestReverseEndPoint: with pytest.raises(FakeError, match='^400 -- (?s:.*)missing'): await glue.reverse_endpoint(napi.NominatimAPIAsync(), a) - - @pytest.mark.asyncio - @pytest.mark.parametrize('params', [{'lat': '45.6', 'lon': '4563'}]) - async def test_reverse_success(self, params): - a = FakeAdaptor() - a.params = params - a.params['format'] = 'json' - - res = await glue.reverse_endpoint(napi.NominatimAPIAsync(), a) - - assert res == '' - - @pytest.mark.asyncio async def test_reverse_success(self): a = FakeAdaptor() @@ -392,7 +367,6 @@ class TestReverseEndPoint: assert await glue.reverse_endpoint(napi.NominatimAPIAsync(), a) - @pytest.mark.asyncio async def test_reverse_from_search(self): a = FakeAdaptor() @@ -413,12 +387,12 @@ class TestLookupEndpoint: self.results = [napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, 2.0))] + async def _lookup(*args, **kwargs): return napi.SearchResults(self.results) monkeypatch.setattr(napi.NominatimAPIAsync, 'lookup', _lookup) - @pytest.mark.asyncio async def test_lookup_no_params(self): a = FakeAdaptor() @@ -428,7 +402,6 @@ class TestLookupEndpoint: assert res.output == '[]' - @pytest.mark.asyncio @pytest.mark.parametrize('param', ['w', 'bad', '']) async def test_lookup_bad_params(self, param): @@ -440,7 +413,6 @@ class TestLookupEndpoint: assert len(json.loads(res.output)) == 1 - @pytest.mark.asyncio @pytest.mark.parametrize('param', ['p234234', '4563']) async def test_lookup_bad_osm_type(self, param): @@ -452,7 +424,6 @@ class TestLookupEndpoint: assert len(json.loads(res.output)) == 1 - @pytest.mark.asyncio async def test_lookup_working(self): a = FakeAdaptor() @@ -473,12 +444,12 @@ class TestSearchEndPointSearch: self.results = [napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, 2.0))] + async def _search(*args, **kwargs): return napi.SearchResults(self.results) monkeypatch.setattr(napi.NominatimAPIAsync, 'search', _search) - @pytest.mark.asyncio async def test_search_free_text(self): a = FakeAdaptor() @@ -488,7 +459,6 @@ class TestSearchEndPointSearch: assert len(json.loads(res.output)) == 1 - @pytest.mark.asyncio async def test_search_free_text_xml(self): a = FakeAdaptor() @@ -500,7 +470,6 @@ class TestSearchEndPointSearch: assert res.status == 200 assert res.output.index('something') > 0 - @pytest.mark.asyncio async def test_search_free_and_structured(self): a = FakeAdaptor() @@ -508,8 +477,7 @@ class TestSearchEndPointSearch: a.params['city'] = 'ignored' with pytest.raises(FakeError, match='^400 -- .*cannot be used together'): - res = await glue.search_endpoint(napi.NominatimAPIAsync(), a) - + await glue.search_endpoint(napi.NominatimAPIAsync(), a) @pytest.mark.asyncio @pytest.mark.parametrize('dedupe,numres', [(True, 1), (False, 2)]) @@ -532,12 +500,12 @@ class TestSearchEndPointSearchAddress: self.results = [napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, 2.0))] + async def _search(*args, **kwargs): return napi.SearchResults(self.results) monkeypatch.setattr(napi.NominatimAPIAsync, 'search_address', _search) - @pytest.mark.asyncio async def test_search_structured(self): a = FakeAdaptor() @@ -555,12 +523,12 @@ class TestSearchEndPointSearchCategory: self.results = [napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, 2.0))] + async def _search(*args, **kwargs): return napi.SearchResults(self.results) monkeypatch.setattr(napi.NominatimAPIAsync, 'search_category', _search) - @pytest.mark.asyncio async def test_search_category(self): a = FakeAdaptor() diff --git a/test/python/api/test_warm.py b/test/python/api/test_warm.py index f0c9986d..94081d00 100644 --- a/test/python/api/test_warm.py +++ b/test/python/api/test_warm.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for warm-up CLI function. @@ -11,6 +11,7 @@ import pytest import nominatim_db.cli + @pytest.fixture(autouse=True) def setup_database_with_context(apiobj, table_factory): table_factory('word', @@ -21,8 +22,9 @@ def setup_database_with_context(apiobj, table_factory): apiobj.add_data('properties', [{'property': 'tokenizer', 'value': 'icu'}, {'property': 'tokenizer_import_normalisation', 'value': ':: lower();'}, - {'property': 'tokenizer_import_transliteration', 'value': "'1' > '/1/'; 'ä' > 'ä '"}, - ]) + {'property': 'tokenizer_import_transliteration', + 'value': "'1' > '/1/'; 'ä' > 'ä '"} + ]) @pytest.mark.parametrize('args', [['--search-only'], ['--reverse-only']]) diff --git a/test/python/cli/conftest.py b/test/python/cli/conftest.py index 84f2d659..921a3a32 100644 --- a/test/python/cli/conftest.py +++ b/test/python/cli/conftest.py @@ -2,12 +2,13 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. import pytest import nominatim_db.cli + class MockParamCapture: """ Mock that records the parameters with which a function was called as well as the number of calls. diff --git a/test/python/cli/test_cli.py b/test/python/cli/test_cli.py index d42df50a..a538049e 100644 --- a/test/python/cli/test_cli.py +++ b/test/python/cli/test_cli.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for command line interface wrapper. @@ -11,7 +11,6 @@ These tests just check that the various command line parameters route to the correct functionality. They use a lot of monkeypatching to avoid executing the actual functions. """ -import importlib import pytest import nominatim_db.indexer.indexer @@ -28,6 +27,7 @@ def test_cli_help(cli_call, capsys): captured = capsys.readouterr() assert captured.out.startswith('usage:') + def test_cli_version(cli_call, capsys): """ Running nominatim tool --version prints a version string. """ @@ -46,7 +46,6 @@ class TestCliWithDb: # Make sure tools.freeze.is_frozen doesn't report database as frozen. Monkeypatching failed table_factory('place') - @pytest.mark.parametrize("name,oid", [('file', 'foo.osm'), ('diff', 'foo.osc')]) def test_cli_add_data_file_command(self, cli_call, mock_func_factory, name, oid): mock_run_legacy = mock_func_factory(nominatim_db.tools.add_osm_data, 'add_data_from_file') @@ -54,7 +53,6 @@ class TestCliWithDb: assert mock_run_legacy.called == 1 - @pytest.mark.parametrize("name,oid", [('node', 12), ('way', 8), ('relation', 32)]) def test_cli_add_data_object_command(self, cli_call, mock_func_factory, name, oid): mock_run_legacy = mock_func_factory(nominatim_db.tools.add_osm_data, 'add_osm_object') @@ -62,8 +60,6 @@ class TestCliWithDb: assert mock_run_legacy.called == 1 - - def test_cli_add_data_tiger_data(self, cli_call, cli_tokenizer_mock, async_mock_func_factory): mock = async_mock_func_factory(nominatim_db.tools.tiger_data, 'add_tiger_data') @@ -80,7 +76,6 @@ class TestCliWithDb: assert mock_drop.called == 1 assert mock_flatnode.called == 1 - @pytest.mark.parametrize("params,do_bnds,do_ranks", [ ([], 2, 2), (['--boundaries-only'], 2, 0), @@ -89,11 +84,14 @@ class TestCliWithDb: def test_index_command(self, monkeypatch, async_mock_func_factory, table_factory, params, do_bnds, do_ranks): table_factory('import_status', 'indexed bool') - bnd_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_boundaries') - rank_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_by_rank') - postcode_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_postcodes') - - monkeypatch.setattr(nominatim_db.indexer.indexer.Indexer, 'has_pending', + bnd_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, + 'index_boundaries') + rank_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, + 'index_by_rank') + postcode_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, + 'index_postcodes') + + monkeypatch.setattr(nominatim_db.indexer.indexer.Indexer, 'has_pending', [False, True].pop) assert self.call_nominatim('index', *params) == 0 @@ -102,7 +100,6 @@ class TestCliWithDb: assert rank_mock.called == do_ranks assert postcode_mock.called == do_ranks - def test_special_phrases_wiki_command(self, mock_func_factory): func = mock_func_factory(nominatim_db.clicmd.special_phrases.SPImporter, 'import_phrases') @@ -110,7 +107,6 @@ class TestCliWithDb: assert func.called == 1 - def test_special_phrases_csv_command(self, src_dir, mock_func_factory): func = mock_func_factory(nominatim_db.clicmd.special_phrases.SPImporter, 'import_phrases') testdata = src_dir / 'test' / 'testdb' @@ -120,7 +116,6 @@ class TestCliWithDb: assert func.called == 1 - def test_special_phrases_csv_bad_file(self, src_dir): testdata = src_dir / 'something349053905.csv' diff --git a/test/python/cli/test_cmd_admin.py b/test/python/cli/test_cmd_admin.py index 7b0b9cd4..9732d734 100644 --- a/test/python/cli/test_cmd_admin.py +++ b/test/python/cli/test_cmd_admin.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Test for the command line interface wrapper admin subcommand. @@ -39,11 +39,13 @@ def test_admin_clean_deleted_relations(cli_call, mock_func_factory): assert cli_call('admin', '--clean-deleted', '1 month') == 0 assert mock.called == 1 + def test_admin_clean_deleted_relations_no_age(cli_call, mock_func_factory): - mock = mock_func_factory(nominatim_db.tools.admin, 'clean_deleted_relations') + mock_func_factory(nominatim_db.tools.admin, 'clean_deleted_relations') assert cli_call('admin', '--clean-deleted') == 1 + class TestCliAdminWithDb: @pytest.fixture(autouse=True) @@ -51,7 +53,6 @@ class TestCliAdminWithDb: self.call_nominatim = cli_call self.tokenizer_mock = cli_tokenizer_mock - @pytest.mark.parametrize("func, params", [('analyse_indexing', ('--analyse-indexing', ))]) def test_analyse_indexing(self, mock_func_factory, func, params): mock = mock_func_factory(nominatim_db.tools.admin, func) diff --git a/test/python/cli/test_cmd_api.py b/test/python/cli/test_cmd_api.py index 1c0750d1..541b680c 100644 --- a/test/python/cli/test_cmd_api.py +++ b/test/python/cli/test_cmd_api.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for API access commands of command-line interface wrapper. @@ -10,9 +10,9 @@ Tests for API access commands of command-line interface wrapper. import json import pytest -import nominatim_db.clicmd.api import nominatim_api as napi + @pytest.mark.parametrize('call', ['search', 'reverse', 'lookup', 'details', 'status']) def test_list_format(cli_call, call): assert 0 == cli_call(call, '--list-formats') @@ -30,13 +30,11 @@ class TestCliStatusCall: monkeypatch.setattr(napi.NominatimAPI, 'status', lambda self: napi.StatusResult(200, 'OK')) - def test_status_simple(self, cli_call, tmp_path): result = cli_call('status', '--project-dir', str(tmp_path)) assert result == 0 - def test_status_json_format(self, cli_call, tmp_path, capsys): result = cli_call('status', '--project-dir', str(tmp_path), '--format', 'json') @@ -60,7 +58,6 @@ class TestCliDetailsCall: ('--way', '1'), ('--relation', '1'), ('--place_id', '10001')]) - def test_details_json_format(self, cli_call, tmp_path, capsys, params): result = cli_call('details', '--project-dir', str(tmp_path), *params) @@ -75,15 +72,14 @@ class TestCliReverseCall: def setup_reverse_mock(self, monkeypatch): result = napi.ReverseResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, -3.0), - names={'name':'Name', 'name:fr': 'Nom'}, - extratags={'extra':'Extra'}, + names={'name': 'Name', 'name:fr': 'Nom'}, + extratags={'extra': 'Extra'}, locale_name='Name', display_name='Name') monkeypatch.setattr(napi.NominatimAPI, 'reverse', lambda *args, **kwargs: result) - def test_reverse_simple(self, cli_call, tmp_path, capsys): result = cli_call('reverse', '--project-dir', str(tmp_path), '--lat', '34', '--lon', '34') @@ -96,7 +92,6 @@ class TestCliReverseCall: assert 'extratags' not in out assert 'namedetails' not in out - @pytest.mark.parametrize('param,field', [('--addressdetails', 'address'), ('--extratags', 'extratags'), ('--namedetails', 'namedetails')]) @@ -109,7 +104,6 @@ class TestCliReverseCall: out = json.loads(capsys.readouterr().out) assert field in out - def test_reverse_format(self, cli_call, tmp_path, capsys): result = cli_call('reverse', '--project-dir', str(tmp_path), '--lat', '34', '--lon', '34', '--format', 'geojson') @@ -125,11 +119,11 @@ class TestCliLookupCall: @pytest.fixture(autouse=True) def setup_lookup_mock(self, monkeypatch): result = napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'), - napi.Point(1.0, -3.0), - names={'name':'Name', 'name:fr': 'Nom'}, - extratags={'extra':'Extra'}, - locale_name='Name', - display_name='Name') + napi.Point(1.0, -3.0), + names={'name': 'Name', 'name:fr': 'Nom'}, + extratags={'extra': 'Extra'}, + locale_name='Name', + display_name='Name') monkeypatch.setattr(napi.NominatimAPI, 'lookup', lambda *args, **kwargs: napi.SearchResults([result])) @@ -150,19 +144,18 @@ class TestCliLookupCall: @pytest.mark.parametrize('endpoint, params', [('search', ('--query', 'Berlin')), ('search_address', ('--city', 'Berlin')) - ]) + ]) def test_search(cli_call, tmp_path, capsys, monkeypatch, endpoint, params): result = napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'), napi.Point(1.0, -3.0), - names={'name':'Name', 'name:fr': 'Nom'}, - extratags={'extra':'Extra'}, + names={'name': 'Name', 'name:fr': 'Nom'}, + extratags={'extra': 'Extra'}, locale_name='Name', display_name='Name') monkeypatch.setattr(napi.NominatimAPI, endpoint, lambda *args, **kwargs: napi.SearchResults([result])) - result = cli_call('search', '--project-dir', str(tmp_path), *params) assert result == 0 diff --git a/test/python/cli/test_cmd_import.py b/test/python/cli/test_cmd_import.py index f833dde3..e4a86fe0 100644 --- a/test/python/cli/test_cmd_import.py +++ b/test/python/cli/test_cmd_import.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for import command of the command-line interface wrapper. @@ -24,15 +24,12 @@ class TestCliImportWithDb: self.call_nominatim = cli_call self.tokenizer_mock = cli_tokenizer_mock - def test_import_missing_file(self): assert self.call_nominatim('import', '--osm-file', 'sfsafegwedgw.reh.erh') == 1 - def test_import_bad_file(self): assert self.call_nominatim('import', '--osm-file', '.') == 1 - @pytest.mark.parametrize('with_updates', [True, False]) def test_import_full(self, mock_func_factory, async_mock_func_factory, with_updates, place_table, property_table): @@ -62,7 +59,6 @@ class TestCliImportWithDb: cf_mock = mock_func_factory(nominatim_db.tools.refresh, 'create_functions') - assert self.call_nominatim(*params) == 0 assert self.tokenizer_mock.finalize_import_called @@ -71,7 +67,6 @@ class TestCliImportWithDb: for mock in mocks: assert mock.called == 1, "Mock '{}' not called".format(mock.func_name) - def test_import_continue_load_data(self, mock_func_factory, async_mock_func_factory): mocks = [ mock_func_factory(nominatim_db.tools.database_import, 'truncate_data_tables'), @@ -89,7 +84,6 @@ class TestCliImportWithDb: for mock in mocks: assert mock.called == 1, "Mock '{}' not called".format(mock.func_name) - def test_import_continue_indexing(self, mock_func_factory, async_mock_func_factory, placex_table, temp_db_conn): mocks = [ @@ -107,7 +101,6 @@ class TestCliImportWithDb: # Calling it again still works for the index assert self.call_nominatim('import', '--continue', 'indexing') == 0 - def test_import_continue_postprocess(self, mock_func_factory, async_mock_func_factory): mocks = [ async_mock_func_factory(nominatim_db.tools.database_import, 'create_search_indices'), diff --git a/test/python/cli/test_cmd_refresh.py b/test/python/cli/test_cmd_refresh.py index 9f3d7bb2..8121946f 100644 --- a/test/python/cli/test_cmd_refresh.py +++ b/test/python/cli/test_cmd_refresh.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for command line interface wrapper for refresk command. @@ -13,6 +13,7 @@ import nominatim_db.tools.refresh import nominatim_db.tools.postcodes import nominatim_db.indexer.indexer + class TestRefresh: @pytest.fixture(autouse=True) @@ -20,7 +21,6 @@ class TestRefresh: self.call_nominatim = cli_call self.tokenizer_mock = cli_tokenizer_mock - @pytest.mark.parametrize("command,func", [ ('address-levels', 'load_address_levels_from_config'), ('wiki-data', 'import_wikipedia_articles'), @@ -33,17 +33,14 @@ class TestRefresh: assert self.call_nominatim('refresh', '--' + command) == 0 assert func_mock.called == 1 - def test_refresh_word_count(self): assert self.call_nominatim('refresh', '--word-count') == 0 assert self.tokenizer_mock.update_statistics_called - def test_refresh_word_tokens(self): assert self.call_nominatim('refresh', '--word-tokens') == 0 assert self.tokenizer_mock.update_word_tokens_called - def test_refresh_postcodes(self, async_mock_func_factory, mock_func_factory, place_table): func_mock = mock_func_factory(nominatim_db.tools.postcodes, 'update_postcodes') idx_mock = async_mock_func_factory(nominatim_db.indexer.indexer.Indexer, 'index_postcodes') @@ -52,12 +49,10 @@ class TestRefresh: assert func_mock.called == 1 assert idx_mock.called == 1 - def test_refresh_postcodes_no_place_table(self): # Do nothing without the place table assert self.call_nominatim('refresh', '--postcodes') == 0 - def test_refresh_create_functions(self, mock_func_factory): func_mock = mock_func_factory(nominatim_db.tools.refresh, 'create_functions') @@ -65,17 +60,14 @@ class TestRefresh: assert func_mock.called == 1 assert self.tokenizer_mock.update_sql_functions_called - def test_refresh_wikidata_file_not_found(self, monkeypatch): monkeypatch.setenv('NOMINATIM_WIKIPEDIA_DATA_PATH', 'gjoiergjeroi345Q') assert self.call_nominatim('refresh', '--wiki-data') == 1 - def test_refresh_secondary_importance_file_not_found(self): assert self.call_nominatim('refresh', '--secondary-importance') == 1 - def test_refresh_secondary_importance_new_table(self, mock_func_factory): mocks = [mock_func_factory(nominatim_db.tools.refresh, 'import_secondary_importance'), mock_func_factory(nominatim_db.tools.refresh, 'create_functions')] @@ -84,7 +76,6 @@ class TestRefresh: assert mocks[0].called == 1 assert mocks[1].called == 1 - def test_refresh_importance_computed_after_wiki_import(self, monkeypatch, mock_func_factory): calls = [] monkeypatch.setattr(nominatim_db.tools.refresh, 'import_wikipedia_articles', @@ -102,7 +93,8 @@ class TestRefresh: ('--data-object', 'N23', '--data-object', 'N24'), ('--data-area', 'R7723'), ('--data-area', 'r7723', '--data-area', 'r2'), - ('--data-area', 'R9284425', '--data-object', 'n1234567894567')]) + ('--data-area', 'R9284425', + '--data-object', 'n1234567894567')]) def test_refresh_objects(self, params, mock_func_factory): func_mock = mock_func_factory(nominatim_db.tools.refresh, 'invalidate_osm_object') @@ -110,7 +102,6 @@ class TestRefresh: assert func_mock.called == len(params)/2 - @pytest.mark.parametrize('func', ('--data-object', '--data-area')) @pytest.mark.parametrize('param', ('234', 'a55', 'R 453', 'Rel')) def test_refresh_objects_bad_param(self, func, param, mock_func_factory): diff --git a/test/python/cli/test_cmd_replication.py b/test/python/cli/test_cmd_replication.py index 21c6350d..34aafa3a 100644 --- a/test/python/cli/test_cmd_replication.py +++ b/test/python/cli/test_cmd_replication.py @@ -18,6 +18,7 @@ import nominatim_db.tools.replication import nominatim_db.tools.refresh from nominatim_db.db import status + @pytest.fixture def tokenizer_mock(monkeypatch): class DummyTokenizer: @@ -40,7 +41,6 @@ def tokenizer_mock(monkeypatch): return tok - @pytest.fixture def init_status(temp_db_conn, status_table): status.set_status(temp_db_conn, date=dt.datetime.now(dt.timezone.utc), seq=1) @@ -62,16 +62,14 @@ class TestCliReplication: def setup_cli_call(self, cli_call, temp_db): self.call_nominatim = lambda *args: cli_call('replication', *args) - @pytest.fixture(autouse=True) def setup_update_function(self, monkeypatch): def _mock_updates(states): monkeypatch.setattr(nominatim_db.tools.replication, 'update', - lambda *args, **kwargs: states.pop()) + lambda *args, **kwargs: states.pop()) self.update_states = _mock_updates - @pytest.mark.parametrize("params,func", [ (('--init',), 'init_replication'), (('--init', '--no-update-functions'), 'init_replication'), @@ -88,20 +86,17 @@ class TestCliReplication: if params == ('--init',): assert umock.called == 1 - def test_replication_update_bad_interval(self, monkeypatch): monkeypatch.setenv('NOMINATIM_REPLICATION_UPDATE_INTERVAL', 'xx') assert self.call_nominatim() == 1 - def test_replication_update_bad_interval_for_geofabrik(self, monkeypatch): monkeypatch.setenv('NOMINATIM_REPLICATION_URL', 'https://download.geofabrik.de/europe/italy-updates') assert self.call_nominatim() == 1 - def test_replication_update_continuous_no_index(self): assert self.call_nominatim('--no-index') == 1 @@ -110,14 +105,12 @@ class TestCliReplication: assert str(update_mock.last_args[1]['osm2pgsql']).endswith('OSM2PGSQL NOT AVAILABLE') - def test_replication_update_custom_osm2pgsql(self, monkeypatch, update_mock): monkeypatch.setenv('NOMINATIM_OSM2PGSQL_BINARY', '/secret/osm2pgsql') assert self.call_nominatim('--once', '--no-index') == 0 assert str(update_mock.last_args[1]['osm2pgsql']) == '/secret/osm2pgsql' - @pytest.mark.parametrize("update_interval", [60, 3600]) def test_replication_catchup(self, placex_table, monkeypatch, index_mock, update_interval): monkeypatch.setenv('NOMINATIM_REPLICATION_UPDATE_INTERVAL', str(update_interval)) @@ -125,13 +118,11 @@ class TestCliReplication: assert self.call_nominatim('--catch-up') == 0 - def test_replication_update_custom_threads(self, update_mock): assert self.call_nominatim('--once', '--no-index', '--threads', '4') == 0 assert update_mock.last_args[1]['threads'] == 4 - def test_replication_update_continuous(self, index_mock): self.update_states([nominatim_db.tools.replication.UpdateState.UP_TO_DATE, nominatim_db.tools.replication.UpdateState.UP_TO_DATE]) @@ -141,7 +132,6 @@ class TestCliReplication: assert index_mock.called == 2 - def test_replication_update_continuous_no_change(self, mock_func_factory, index_mock): self.update_states([nominatim_db.tools.replication.UpdateState.NO_CHANGES, diff --git a/test/python/config/test_config.py b/test/python/config/test_config.py index 9f68fcb9..a0dbf476 100644 --- a/test/python/config/test_config.py +++ b/test/python/config/test_config.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Test for loading dotenv configuration. @@ -13,6 +13,7 @@ import pytest from nominatim_db.config import Configuration, flatten_config_list from nominatim_db.errors import UsageError + @pytest.fixture def make_config(): """ Create a configuration object from the given project directory. @@ -22,6 +23,7 @@ def make_config(): return _mk_config + @pytest.fixture def make_config_path(tmp_path): """ Create a configuration object with project and config directories @@ -108,7 +110,7 @@ def test_get_libpq_dsn_convert_php(make_config, monkeypatch): @pytest.mark.parametrize("val,expect", [('foo bar', "'foo bar'"), ("xy'z", "xy\\'z"), - ]) + ]) def test_get_libpq_dsn_convert_php_special_chars(make_config, monkeypatch, val, expect): config = make_config() @@ -137,6 +139,7 @@ def test_get_bool(make_config, monkeypatch, value, result): assert config.get_bool('FOOBAR') == result + def test_get_bool_empty(make_config): config = make_config() @@ -303,7 +306,7 @@ def test_load_subconf_env_absolute_not_found(make_config_path, monkeypatch, tmp_ (config.config_dir / 'test.yaml').write_text('cow: muh\ncat: miau\n') with pytest.raises(UsageError, match='Config file not found.'): - rules = config.load_sub_configuration('test.yaml', config='MY_CONFIG') + config.load_sub_configuration('test.yaml', config='MY_CONFIG') @pytest.mark.parametrize("location", ['project_dir', 'config_dir']) @@ -326,7 +329,7 @@ def test_load_subconf_env_relative_not_found(make_config_path, monkeypatch): (config.config_dir / 'test.yaml').write_text('cow: muh\ncat: miau\n') with pytest.raises(UsageError, match='Config file not found.'): - rules = config.load_sub_configuration('test.yaml', config='MY_CONFIG') + config.load_sub_configuration('test.yaml', config='MY_CONFIG') def test_load_subconf_json(make_config_path): @@ -338,6 +341,7 @@ def test_load_subconf_json(make_config_path): assert rules == dict(cow='muh', cat='miau') + def test_load_subconf_not_found(make_config_path): config = make_config_path() @@ -371,7 +375,7 @@ def test_load_subconf_include_relative(make_config_path, tmp_path, location): config = make_config_path() testfile = config.config_dir / 'test.yaml' - testfile.write_text(f'base: !include inc.yaml\n') + testfile.write_text('base: !include inc.yaml\n') (getattr(config, location) / 'inc.yaml').write_text('first: 1\nsecond: 2\n') rules = config.load_sub_configuration('test.yaml') @@ -383,28 +387,28 @@ def test_load_subconf_include_bad_format(make_config_path): config = make_config_path() testfile = config.config_dir / 'test.yaml' - testfile.write_text(f'base: !include inc.txt\n') + testfile.write_text('base: !include inc.txt\n') (config.config_dir / 'inc.txt').write_text('first: 1\nsecond: 2\n') with pytest.raises(UsageError, match='Cannot handle config file format.'): - rules = config.load_sub_configuration('test.yaml') + config.load_sub_configuration('test.yaml') def test_load_subconf_include_not_found(make_config_path): config = make_config_path() testfile = config.config_dir / 'test.yaml' - testfile.write_text(f'base: !include inc.txt\n') + testfile.write_text('base: !include inc.txt\n') with pytest.raises(UsageError, match='Config file not found.'): - rules = config.load_sub_configuration('test.yaml') + config.load_sub_configuration('test.yaml') def test_load_subconf_include_recursive(make_config_path): config = make_config_path() testfile = config.config_dir / 'test.yaml' - testfile.write_text(f'base: !include inc.yaml\n') + testfile.write_text('base: !include inc.yaml\n') (config.config_dir / 'inc.yaml').write_text('- !include more.yaml\n- upper\n') (config.config_dir / 'more.yaml').write_text('- the end\n') @@ -435,6 +439,6 @@ def test_flatten_config_list_nested(): [[2, 3], [45, [56, 78], 66]], 'end' ] + assert flatten_config_list(content) == \ - [34, {'first': '1st', 'second': '2nd'}, {}, - 2, 3, 45, 56, 78, 66, 'end'] + [34, {'first': '1st', 'second': '2nd'}, {}, 2, 3, 45, 56, 78, 66, 'end'] diff --git a/test/python/config/test_config_load_module.py b/test/python/config/test_config_load_module.py index c2912180..309bd1fc 100644 --- a/test/python/config/test_config_load_module.py +++ b/test/python/config/test_config_load_module.py @@ -2,18 +2,18 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Test for loading extra Python modules. """ -from pathlib import Path import sys import pytest from nominatim_db.config import Configuration + @pytest.fixture def test_config(src_dir, tmp_path): """ Create a configuration object with project and config directories @@ -31,6 +31,7 @@ def test_load_default_module(test_config): assert isinstance(module.NOMINATIM_VERSION, tuple) + def test_load_default_module_with_hyphen(test_config): module = test_config.load_plugin_module('place-info', 'nominatim_db.data') diff --git a/test/python/conftest.py b/test/python/conftest.py index a25ff8ec..f95da39e 100644 --- a/test/python/conftest.py +++ b/test/python/conftest.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. import itertools import sys @@ -69,6 +69,7 @@ def temp_db_with_extensions(temp_db): return temp_db + @pytest.fixture def temp_db_conn(temp_db): """ Connection to the test database. @@ -100,8 +101,9 @@ def table_factory(temp_db_conn): if content: sql = pysql.SQL("INSERT INTO {} VALUES ({})")\ .format(pysql.Identifier(name), - pysql.SQL(',').join([pysql.Placeholder() for _ in range(len(content[0]))])) - cur.executemany(sql , content) + pysql.SQL(',').join([pysql.Placeholder() + for _ in range(len(content[0]))])) + cur.executemany(sql, content) return mk_table @@ -178,6 +180,7 @@ def place_row(place_table, temp_db_cursor): return _insert + @pytest.fixture def placex_table(temp_db_with_extensions, temp_db_conn): """ Create an empty version of the place table. diff --git a/test/python/cursor.py b/test/python/cursor.py index b3fc260a..5dc93cd5 100644 --- a/test/python/cursor.py +++ b/test/python/cursor.py @@ -2,13 +2,14 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Specialised psycopg cursor with shortcut functions useful for testing. """ import psycopg + class CursorForTesting(psycopg.Cursor): """ Extension to the DictCursor class that provides execution short-cuts that simplify writing assertions. @@ -22,7 +23,6 @@ class CursorForTesting(psycopg.Cursor): assert self.rowcount == 1 return self.fetchone()[0] - def row_set(self, sql, params=None): """ Execute a query and return the result as a set of tuples. Fails when the SQL command returns duplicate rows. @@ -34,7 +34,6 @@ class CursorForTesting(psycopg.Cursor): return result - def table_exists(self, table): """ Check that a table with the given name exists in the database. """ @@ -42,7 +41,6 @@ class CursorForTesting(psycopg.Cursor): WHERE tablename = %s""", (table, )) return num == 1 - def index_exists(self, table, index): """ Check that an indexwith the given name exists on the given table. """ @@ -51,7 +49,6 @@ class CursorForTesting(psycopg.Cursor): (table, index)) return num == 1 - def table_rows(self, table, where=None): """ Return the number of rows in the given table. """ diff --git a/test/python/data/test_country_info.py b/test/python/data/test_country_info.py index 14b306bb..a85b7bf9 100644 --- a/test/python/data/test_country_info.py +++ b/test/python/data/test_country_info.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for function that handle country properties. @@ -12,6 +12,7 @@ import pytest from nominatim_db.data import country_info + @pytest.fixture def loaded_country(def_config): country_info.setup_country_config(def_config) @@ -115,8 +116,8 @@ def test_setup_country_config_languages_not_loaded(env_with_country_config): info = country_info._CountryInfo() info.load(config) assert dict(info.items()) == {'de': {'partition': 3, - 'languages': [], - 'names': {'name': 'Deutschland'}}} + 'languages': [], + 'names': {'name': 'Deutschland'}}} def test_setup_country_config_name_not_loaded(env_with_country_config): @@ -132,8 +133,7 @@ def test_setup_country_config_name_not_loaded(env_with_country_config): assert dict(info.items()) == {'de': {'partition': 3, 'languages': ['de'], - 'names': {} - }} + 'names': {}}} def test_setup_country_config_names_not_loaded(env_with_country_config): @@ -148,8 +148,7 @@ def test_setup_country_config_names_not_loaded(env_with_country_config): assert dict(info.items()) == {'de': {'partition': 3, 'languages': ['de'], - 'names': {} - }} + 'names': {}}} def test_setup_country_config_special_character(env_with_country_config): @@ -157,8 +156,8 @@ def test_setup_country_config_special_character(env_with_country_config): bq: partition: 250 languages: nl - names: - name: + names: + name: default: "\\N" """) @@ -167,5 +166,4 @@ def test_setup_country_config_special_character(env_with_country_config): assert dict(info.items()) == {'bq': {'partition': 250, 'languages': ['nl'], - 'names': {'name': '\x85'} - }} + 'names': {'name': '\x85'}}} diff --git a/test/python/db/test_connection.py b/test/python/db/test_connection.py index a8b5d677..19b945fd 100644 --- a/test/python/db/test_connection.py +++ b/test/python/db/test_connection.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for specialised connection and cursor classes. @@ -12,6 +12,7 @@ import psycopg import nominatim_db.db.connection as nc + @pytest.fixture def db(dsn): with nc.connect(dsn) as conn: @@ -36,6 +37,7 @@ def test_has_column(db, table_factory, name, result): assert nc.table_has_column(db, 'stuff', name) == result + def test_connection_index_exists(db, table_factory, temp_db_cursor): assert not nc.index_exists(db, 'some_index') @@ -76,6 +78,7 @@ def test_drop_table_non_existing_force(db): with pytest.raises(psycopg.ProgrammingError, match='.*does not exist.*'): nc.drop_tables(db, 'dfkjgjriogjigjgjrdghehtre', if_exists=False) + def test_connection_server_version_tuple(db): ver = nc.server_version_tuple(db) diff --git a/test/python/db/test_properties.py b/test/python/db/test_properties.py index e55bb973..84d7dae0 100644 --- a/test/python/db/test_properties.py +++ b/test/python/db/test_properties.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for property table manpulation. @@ -11,6 +11,7 @@ import pytest from nominatim_db.db import properties + @pytest.fixture def property_factory(property_table, temp_db_cursor): """ A function fixture that adds a property into the property table. diff --git a/test/python/db/test_sql_preprocessor.py b/test/python/db/test_sql_preprocessor.py index 45109c70..f2fbbb2a 100644 --- a/test/python/db/test_sql_preprocessor.py +++ b/test/python/db/test_sql_preprocessor.py @@ -2,16 +2,17 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for SQL preprocessing. """ import pytest -import pytest_asyncio +import pytest_asyncio # noqa from nominatim_db.db.sql_preprocessor import SQLPreprocessor + @pytest.fixture def sql_factory(tmp_path): def _mk_sql(sql_body): @@ -26,6 +27,7 @@ def sql_factory(tmp_path): return _mk_sql + @pytest.mark.parametrize("expr,ret", [ ("'a'", 'a'), ("'{{db.partitions|join}}'", '012'), @@ -61,8 +63,7 @@ def test_load_file_with_params(sql_preprocessor, sql_factory, temp_db_conn, temp async def test_load_parallel_file(dsn, sql_preprocessor, tmp_path, temp_db_cursor): (tmp_path / 'test.sql').write_text(""" CREATE TABLE foo (a TEXT); - CREATE TABLE foo2(a TEXT);""" + - "\n---\nCREATE TABLE bar (b INT);") + CREATE TABLE foo2(a TEXT);""" + "\n---\nCREATE TABLE bar (b INT);") await sql_preprocessor.run_parallel_sql_file(dsn, 'test.sql', num_threads=4) diff --git a/test/python/db/test_status.py b/test/python/db/test_status.py index 77135a8c..462b8e3d 100644 --- a/test/python/db/test_status.py +++ b/test/python/db/test_status.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for status table manipulation. @@ -19,7 +19,8 @@ OSM_NODE_DATA = """\ -""" +""" # noqa + def iso_date(date): return dt.datetime.strptime(date, nominatim_db.db.status.ISODATE_FORMAT)\ @@ -43,7 +44,8 @@ def test_compute_database_date_from_osm2pgsql(table_factory, temp_db_conn, offli def test_compute_database_date_from_osm2pgsql_nodata(table_factory, temp_db_conn): table_factory('osm2pgsql_properties', 'property TEXT, value TEXT') - with pytest.raises(UsageError, match='Cannot determine database date from data in offline mode'): + with pytest.raises(UsageError, + match='Cannot determine database date from data in offline mode'): nominatim_db.db.status.compute_database_date(temp_db_conn, offline=True) @@ -56,6 +58,7 @@ def test_compute_database_date_valid(monkeypatch, place_row, temp_db_conn): place_row(osm_type='N', osm_id=45673) requested_url = [] + def mock_url(url): requested_url.append(url) return OSM_NODE_DATA @@ -72,6 +75,7 @@ def test_compute_database_broken_api(monkeypatch, place_row, temp_db_conn): place_row(osm_type='N', osm_id=45673) requested_url = [] + def mock_url(url): requested_url.append(url) return ' ' '"), sanitizers=[hnr], with_housenumber=True) as anl: + with analyzer(trans=(":: upper()", "'🜵' > ' '"), sanitizers=[hnr], + with_housenumber=True) as anl: self.analyzer = anl yield anl - @pytest.fixture def getorcreate_hnr_id(self, temp_db_cursor): - temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION create_analyzed_hnr_id(norm_term TEXT, lookup_terms TEXT[]) - RETURNS INTEGER AS $$ - SELECT -nextval('seq_word')::INTEGER; $$ LANGUAGE SQL""") - + temp_db_cursor.execute(""" + CREATE OR REPLACE FUNCTION create_analyzed_hnr_id(norm_term TEXT, lookup_terms TEXT[]) + RETURNS INTEGER AS $$ + SELECT -nextval('seq_word')::INTEGER; $$ LANGUAGE SQL""") def process_address(self, **kwargs): return self.analyzer.process_place(PlaceInfo({'address': kwargs})) - def name_token_set(self, *expected_terms): tokens = self.analyzer.get_word_token_info(expected_terms) for token in tokens: @@ -598,7 +570,6 @@ class TestPlaceHousenumberWithAnalyser: return set((t[2] for t in tokens)) - @pytest.mark.parametrize('hnr', ['123 a', '1', '101']) def test_process_place_housenumbers_simple(self, hnr, getorcreate_hnr_id): info = self.process_address(housenumber=hnr) @@ -606,7 +577,6 @@ class TestPlaceHousenumberWithAnalyser: assert info['hnr'] == hnr.upper() assert info['hnr_tokens'] == "{-1}" - def test_process_place_housenumbers_duplicates(self, getorcreate_hnr_id): info = self.process_address(housenumber='134', conscriptionnumber='134', @@ -615,7 +585,6 @@ class TestPlaceHousenumberWithAnalyser: assert set(info['hnr'].split(';')) == set(('134', '99 A')) assert info['hnr_tokens'] == "{-1,-2}" - def test_process_place_housenumbers_cached(self, getorcreate_hnr_id): info = self.process_address(housenumber="45") assert info['hnr_tokens'] == "{-1}" @@ -637,7 +606,6 @@ class TestUpdateWordTokens: table_factory('search_name', 'place_id BIGINT, name_vector INT[]') self.tok = tokenizer_factory() - @pytest.fixture def search_entry(self, temp_db_cursor): place_id = itertools.count(1000) @@ -648,7 +616,6 @@ class TestUpdateWordTokens: return _insert - @pytest.fixture(params=['simple', 'analyzed']) def add_housenumber(self, request, word_table): if request.param == 'simple': @@ -660,7 +627,6 @@ class TestUpdateWordTokens: return _make - @pytest.mark.parametrize('hnr', ('1a', '1234567', '34 5')) def test_remove_unused_housenumbers(self, add_housenumber, word_table, hnr): word_table.add_housenumber(1000, hnr) @@ -669,7 +635,6 @@ class TestUpdateWordTokens: self.tok.update_word_tokens() assert word_table.count_housenumbers() == 0 - def test_keep_unused_numeral_housenumbers(self, add_housenumber, word_table): add_housenumber(1000, '5432') @@ -677,8 +642,8 @@ class TestUpdateWordTokens: self.tok.update_word_tokens() assert word_table.count_housenumbers() == 1 - - def test_keep_housenumbers_from_search_name_table(self, add_housenumber, word_table, search_entry): + def test_keep_housenumbers_from_search_name_table(self, add_housenumber, + word_table, search_entry): add_housenumber(9999, '5432a') add_housenumber(9991, '9 a') search_entry(123, 9999, 34) @@ -687,8 +652,8 @@ class TestUpdateWordTokens: self.tok.update_word_tokens() assert word_table.count_housenumbers() == 1 - - def test_keep_housenumbers_from_placex_table(self, add_housenumber, word_table, placex_table): + def test_keep_housenumbers_from_placex_table(self, add_housenumber, word_table, + placex_table): add_housenumber(9999, '5432a') add_housenumber(9990, '34z') placex_table.add(housenumber='34z') @@ -698,8 +663,8 @@ class TestUpdateWordTokens: self.tok.update_word_tokens() assert word_table.count_housenumbers() == 1 - - def test_keep_housenumbers_from_placex_table_hnr_list(self, add_housenumber, word_table, placex_table): + def test_keep_housenumbers_from_placex_table_hnr_list(self, add_housenumber, + word_table, placex_table): add_housenumber(9991, '9 b') add_housenumber(9990, '34z') placex_table.add(housenumber='9 a;9 b;9 c') diff --git a/test/python/tokenizer/test_icu_rule_loader.py b/test/python/tokenizer/test_icu_rule_loader.py index a3fae758..f26b84c2 100644 --- a/test/python/tokenizer/test_icu_rule_loader.py +++ b/test/python/tokenizer/test_icu_rule_loader.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for converting a config file to ICU rules. @@ -19,17 +19,16 @@ from icu import Transliterator CONFIG_SECTIONS = ('normalization', 'transliteration', 'token-analysis') + class TestIcuRuleLoader: @pytest.fixture(autouse=True) def init_env(self, project_env): self.project_env = project_env - def write_config(self, content): (self.project_env.project_dir / 'icu_tokenizer.yaml').write_text(dedent(content)) - def config_rules(self, *variants): content = dedent("""\ normalization: @@ -49,14 +48,12 @@ class TestIcuRuleLoader: content += '\n'.join((" - " + s for s in variants)) + '\n' self.write_config(content) - def get_replacements(self, *variants): self.config_rules(*variants) loader = ICURuleLoader(self.project_env) rules = loader.analysis[None].config['replacements'] - return sorted((k, sorted(v)) for k,v in rules) - + return sorted((k, sorted(v)) for k, v in rules) def test_empty_rule_set(self): self.write_config("""\ @@ -72,16 +69,14 @@ class TestIcuRuleLoader: assert rules.get_normalization_rules() == '' assert rules.get_transliteration_rules() == '' - @pytest.mark.parametrize("section", CONFIG_SECTIONS) def test_missing_section(self, section): - rule_cfg = { s: [] for s in CONFIG_SECTIONS if s != section} + rule_cfg = {s: [] for s in CONFIG_SECTIONS if s != section} self.write_config(yaml.dump(rule_cfg)) with pytest.raises(UsageError): ICURuleLoader(self.project_env) - def test_get_search_rules(self): self.config_rules() loader = ICURuleLoader(self.project_env) @@ -97,7 +92,6 @@ class TestIcuRuleLoader: assert trans.transliterate(" Αθήνα ") == " athēna " assert trans.transliterate(" проспект ") == " prospekt " - def test_get_normalization_rules(self): self.config_rules() loader = ICURuleLoader(self.project_env) @@ -106,7 +100,6 @@ class TestIcuRuleLoader: assert trans.transliterate(" проспект-Prospekt ") == " проспект prospekt " - def test_get_transliteration_rules(self): self.config_rules() loader = ICURuleLoader(self.project_env) @@ -115,7 +108,6 @@ class TestIcuRuleLoader: assert trans.transliterate(" проспект-Prospekt ") == " prospekt Prospekt " - def test_transliteration_rules_from_file(self): self.write_config("""\ normalization: @@ -135,7 +127,6 @@ class TestIcuRuleLoader: assert trans.transliterate(" axxt ") == " byt " - def test_search_rules(self): self.config_rules('~street => s,st', 'master => mstr') proc = ICURuleLoader(self.project_env).make_token_analysis() @@ -144,7 +135,6 @@ class TestIcuRuleLoader: assert proc.search.transliterate('Earnes St').strip() == 'earnes st' assert proc.search.transliterate('Nostreet').strip() == 'nostreet' - @pytest.mark.parametrize("variant", ['foo > bar', 'foo -> bar -> bar', '~foo~ -> bar', 'fo~ o -> bar']) def test_invalid_variant_description(self, variant): @@ -157,25 +147,21 @@ class TestIcuRuleLoader: assert repl == [(' foo ', [' bar', ' foo'])] - def test_replace_full(self): repl = self.get_replacements("foo => bar") assert repl == [(' foo ', [' bar'])] - def test_add_suffix_no_decompose(self): repl = self.get_replacements("~berg |-> bg") assert repl == [(' berg ', [' berg', ' bg']), ('berg ', ['berg', 'bg'])] - def test_replace_suffix_no_decompose(self): repl = self.get_replacements("~berg |=> bg") - assert repl == [(' berg ', [' bg']),('berg ', ['bg'])] - + assert repl == [(' berg ', [' bg']), ('berg ', ['bg'])] def test_add_suffix_decompose(self): repl = self.get_replacements("~berg -> bg") @@ -183,26 +169,22 @@ class TestIcuRuleLoader: assert repl == [(' berg ', [' berg', ' bg', 'berg', 'bg']), ('berg ', [' berg', ' bg', 'berg', 'bg'])] - def test_replace_suffix_decompose(self): repl = self.get_replacements("~berg => bg") assert repl == [(' berg ', [' bg', 'bg']), ('berg ', [' bg', 'bg'])] - def test_add_prefix_no_compose(self): repl = self.get_replacements("hinter~ |-> hnt") assert repl == [(' hinter', [' hinter', ' hnt']), (' hinter ', [' hinter', ' hnt'])] - def test_replace_prefix_no_compose(self): repl = self.get_replacements("hinter~ |=> hnt") - assert repl == [(' hinter', [' hnt']), (' hinter ', [' hnt'])] - + assert repl == [(' hinter', [' hnt']), (' hinter ', [' hnt'])] def test_add_prefix_compose(self): repl = self.get_replacements("hinter~-> h") @@ -210,45 +192,38 @@ class TestIcuRuleLoader: assert repl == [(' hinter', [' h', ' h ', ' hinter', ' hinter ']), (' hinter ', [' h', ' h', ' hinter', ' hinter'])] - def test_replace_prefix_compose(self): repl = self.get_replacements("hinter~=> h") assert repl == [(' hinter', [' h', ' h ']), (' hinter ', [' h', ' h'])] - def test_add_beginning_only(self): repl = self.get_replacements("^Premier -> Pr") assert repl == [('^ premier ', ['^ pr', '^ premier'])] - def test_replace_beginning_only(self): repl = self.get_replacements("^Premier => Pr") assert repl == [('^ premier ', ['^ pr'])] - def test_add_final_only(self): repl = self.get_replacements("road$ -> rd") assert repl == [(' road ^', [' rd ^', ' road ^'])] - def test_replace_final_only(self): repl = self.get_replacements("road$ => rd") assert repl == [(' road ^', [' rd ^'])] - def test_decompose_only(self): repl = self.get_replacements("~foo -> foo") assert repl == [(' foo ', [' foo', 'foo']), ('foo ', [' foo', 'foo'])] - def test_add_suffix_decompose_end_only(self): repl = self.get_replacements("~berg |-> bg", "~berg$ -> bg") @@ -257,7 +232,6 @@ class TestIcuRuleLoader: ('berg ', ['berg', 'bg']), ('berg ^', [' berg ^', ' bg ^', 'berg ^', 'bg ^'])] - def test_replace_suffix_decompose_end_only(self): repl = self.get_replacements("~berg |=> bg", "~berg$ => bg") @@ -266,7 +240,6 @@ class TestIcuRuleLoader: ('berg ', ['bg']), ('berg ^', [' bg ^', 'bg ^'])] - def test_add_multiple_suffix(self): repl = self.get_replacements("~berg,~burg -> bg") diff --git a/test/python/tokenizer/test_place_sanitizer.py b/test/python/tokenizer/test_place_sanitizer.py index 25844459..fcf02bd3 100644 --- a/test/python/tokenizer/test_place_sanitizer.py +++ b/test/python/tokenizer/test_place_sanitizer.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for execution of the sanitztion step. @@ -50,13 +50,13 @@ def test_placeinfo_has_attr(): def test_sanitizer_default(def_config): san = sanitizer.PlaceSanitizer([{'step': 'split-name-list'}], def_config) - name, address = san.process_names(PlaceInfo({'name': {'name:de:de': '1;2;3'}, - 'address': {'street': 'Bald'}})) + name, address = san.process_names(PlaceInfo({'name': {'name:de:de': '1;2;3'}, + 'address': {'street': 'Bald'}})) assert len(name) == 3 assert all(isinstance(n, sanitizer.PlaceName) for n in name) - assert all(n.kind == 'name' for n in name) - assert all(n.suffix == 'de:de' for n in name) + assert all(n.kind == 'name' for n in name) + assert all(n.suffix == 'de:de' for n in name) assert len(address) == 1 assert all(isinstance(n, sanitizer.PlaceName) for n in address) @@ -66,7 +66,7 @@ def test_sanitizer_default(def_config): def test_sanitizer_empty_list(def_config, rules): san = sanitizer.PlaceSanitizer(rules, def_config) - name, address = san.process_names(PlaceInfo({'name': {'name:de:de': '1;2;3'}})) + name, address = san.process_names(PlaceInfo({'name': {'name:de:de': '1;2;3'}})) assert len(name) == 1 assert all(isinstance(n, sanitizer.PlaceName) for n in name) @@ -74,4 +74,4 @@ def test_sanitizer_empty_list(def_config, rules): def test_sanitizer_missing_step_definition(def_config): with pytest.raises(UsageError): - san = sanitizer.PlaceSanitizer([{'id': 'split-name-list'}], def_config) + sanitizer.PlaceSanitizer([{'id': 'split-name-list'}], def_config) diff --git a/test/python/tokenizer/token_analysis/test_analysis_postcodes.py b/test/python/tokenizer/token_analysis/test_analysis_postcodes.py index 870c8a5d..1eb15a50 100644 --- a/test/python/tokenizer/token_analysis/test_analysis_postcodes.py +++ b/test/python/tokenizer/token_analysis/test_analysis_postcodes.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for special postcode analysis and variant generation. @@ -13,7 +13,6 @@ from icu import Transliterator import nominatim_db.tokenizer.token_analysis.postcodes as module from nominatim_db.data.place_name import PlaceName -from nominatim_db.errors import UsageError DEFAULT_NORMALIZATION = """ :: NFD (); '🜳' > ' '; @@ -27,9 +26,10 @@ DEFAULT_TRANSLITERATION = """ :: Latin (); '🜵' > ' '; """ + @pytest.fixture def analyser(): - rules = { 'analyzer': 'postcodes'} + rules = {'analyzer': 'postcodes'} config = module.configure(rules, DEFAULT_NORMALIZATION) trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION) diff --git a/test/python/tokenizer/token_analysis/test_generic.py b/test/python/tokenizer/token_analysis/test_generic.py index 191f551f..02870f24 100644 --- a/test/python/tokenizer/token_analysis/test_generic.py +++ b/test/python/tokenizer/token_analysis/test_generic.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for import name normalisation and variant generation. @@ -26,8 +26,9 @@ DEFAULT_TRANSLITERATION = """ :: Latin (); '🜵' > ' '; """ + def make_analyser(*variants, variant_only=False): - rules = { 'analyzer': 'generic', 'variants': [{'words': variants}]} + rules = {'analyzer': 'generic', 'variants': [{'words': variants}]} if variant_only: rules['mode'] = 'variant-only' trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION) @@ -43,7 +44,7 @@ def get_normalized_variants(proc, name): def test_no_variants(): - rules = { 'analyzer': 'generic' } + rules = {'analyzer': 'generic'} trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION) norm = Transliterator.createFromRules("test_norm", DEFAULT_NORMALIZATION) config = module.configure(rules, norm, trans) @@ -62,35 +63,36 @@ def test_variants_empty(): VARIANT_TESTS = [ -(('~strasse,~straße -> str', '~weg => weg'), "hallo", {'hallo'}), -(('weg => wg',), "holzweg", {'holzweg'}), -(('weg -> wg',), "holzweg", {'holzweg'}), -(('~weg => weg',), "holzweg", {'holz weg', 'holzweg'}), -(('~weg -> weg',), "holzweg", {'holz weg', 'holzweg'}), -(('~weg => w',), "holzweg", {'holz w', 'holzw'}), -(('~weg -> w',), "holzweg", {'holz weg', 'holzweg', 'holz w', 'holzw'}), -(('~weg => weg',), "Meier Weg", {'meier weg', 'meierweg'}), -(('~weg -> weg',), "Meier Weg", {'meier weg', 'meierweg'}), -(('~weg => w',), "Meier Weg", {'meier w', 'meierw'}), -(('~weg -> w',), "Meier Weg", {'meier weg', 'meierweg', 'meier w', 'meierw'}), -(('weg => wg',), "Meier Weg", {'meier wg'}), -(('weg -> wg',), "Meier Weg", {'meier weg', 'meier wg'}), -(('~strasse,~straße -> str', '~weg => weg'), "Bauwegstraße", + (('~strasse,~straße -> str', '~weg => weg'), "hallo", {'hallo'}), + (('weg => wg',), "holzweg", {'holzweg'}), + (('weg -> wg',), "holzweg", {'holzweg'}), + (('~weg => weg',), "holzweg", {'holz weg', 'holzweg'}), + (('~weg -> weg',), "holzweg", {'holz weg', 'holzweg'}), + (('~weg => w',), "holzweg", {'holz w', 'holzw'}), + (('~weg -> w',), "holzweg", {'holz weg', 'holzweg', 'holz w', 'holzw'}), + (('~weg => weg',), "Meier Weg", {'meier weg', 'meierweg'}), + (('~weg -> weg',), "Meier Weg", {'meier weg', 'meierweg'}), + (('~weg => w',), "Meier Weg", {'meier w', 'meierw'}), + (('~weg -> w',), "Meier Weg", {'meier weg', 'meierweg', 'meier w', 'meierw'}), + (('weg => wg',), "Meier Weg", {'meier wg'}), + (('weg -> wg',), "Meier Weg", {'meier weg', 'meier wg'}), + (('~strasse,~straße -> str', '~weg => weg'), "Bauwegstraße", {'bauweg straße', 'bauweg str', 'bauwegstraße', 'bauwegstr'}), -(('am => a', 'bach => b'), "am bach", {'a b'}), -(('am => a', '~bach => b'), "am bach", {'a b'}), -(('am -> a', '~bach -> b'), "am bach", {'am bach', 'a bach', 'am b', 'a b'}), -(('am -> a', '~bach -> b'), "ambach", {'ambach', 'am bach', 'amb', 'am b'}), -(('saint -> s,st', 'street -> st'), "Saint Johns Street", + (('am => a', 'bach => b'), "am bach", {'a b'}), + (('am => a', '~bach => b'), "am bach", {'a b'}), + (('am -> a', '~bach -> b'), "am bach", {'am bach', 'a bach', 'am b', 'a b'}), + (('am -> a', '~bach -> b'), "ambach", {'ambach', 'am bach', 'amb', 'am b'}), + (('saint -> s,st', 'street -> st'), "Saint Johns Street", {'saint johns street', 's johns street', 'st johns street', 'saint johns st', 's johns st', 'st johns st'}), -(('river$ -> r',), "River Bend Road", {'river bend road'}), -(('river$ -> r',), "Bent River", {'bent river', 'bent r'}), -(('^north => n',), "North 2nd Street", {'n 2nd street'}), -(('^north => n',), "Airport North", {'airport north'}), -(('am -> a',), "am am am am am am am am", {'am am am am am am am am'}), -(('am => a',), "am am am am am am am am", {'a a a a a a a a'}) -] + (('river$ -> r',), "River Bend Road", {'river bend road'}), + (('river$ -> r',), "Bent River", {'bent river', 'bent r'}), + (('^north => n',), "North 2nd Street", {'n 2nd street'}), + (('^north => n',), "Airport North", {'airport north'}), + (('am -> a',), "am am am am am am am am", {'am am am am am am am am'}), + (('am => a',), "am am am am am am am am", {'a a a a a a a a'}) + ] + @pytest.mark.parametrize("rules,name,variants", VARIANT_TESTS) def test_variants(rules, name, variants): @@ -103,10 +105,11 @@ def test_variants(rules, name, variants): VARIANT_ONLY_TESTS = [ -(('weg => wg',), "hallo", set()), -(('weg => wg',), "Meier Weg", {'meier wg'}), -(('weg -> wg',), "Meier Weg", {'meier wg'}), -] + (('weg => wg',), "hallo", set()), + (('weg => wg',), "Meier Weg", {'meier wg'}), + (('weg -> wg',), "Meier Weg", {'meier wg'}), + ] + @pytest.mark.parametrize("rules,name,variants", VARIANT_ONLY_TESTS) def test_variants_only(rules, name, variants): @@ -122,17 +125,15 @@ class TestGetReplacements: @staticmethod def configure_rules(*variants): - rules = { 'analyzer': 'generic', 'variants': [{'words': variants}]} + rules = {'analyzer': 'generic', 'variants': [{'words': variants}]} trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION) norm = Transliterator.createFromRules("test_norm", DEFAULT_NORMALIZATION) return module.configure(rules, norm, trans) - def get_replacements(self, *variants): config = self.configure_rules(*variants) - return sorted((k, sorted(v)) for k,v in config['replacements']) - + return sorted((k, sorted(v)) for k, v in config['replacements']) @pytest.mark.parametrize("variant", ['foo > bar', 'foo -> bar -> bar', '~foo~ -> bar', 'fo~ o -> bar']) @@ -140,38 +141,32 @@ class TestGetReplacements: with pytest.raises(UsageError): self.configure_rules(variant) - @pytest.mark.parametrize("rule", ["!!! -> bar", "bar => !!!"]) def test_ignore_unnormalizable_terms(self, rule): repl = self.get_replacements(rule) assert repl == [] - def test_add_full(self): repl = self.get_replacements("foo -> bar") assert repl == [(' foo ', [' bar', ' foo'])] - def test_replace_full(self): repl = self.get_replacements("foo => bar") assert repl == [(' foo ', [' bar'])] - def test_add_suffix_no_decompose(self): repl = self.get_replacements("~berg |-> bg") assert repl == [(' berg ', [' berg', ' bg']), ('berg ', ['berg', 'bg'])] - def test_replace_suffix_no_decompose(self): repl = self.get_replacements("~berg |=> bg") - assert repl == [(' berg ', [' bg']),('berg ', ['bg'])] - + assert repl == [(' berg ', [' bg']), ('berg ', ['bg'])] def test_add_suffix_decompose(self): repl = self.get_replacements("~berg -> bg") @@ -179,26 +174,22 @@ class TestGetReplacements: assert repl == [(' berg ', [' berg', ' bg', 'berg', 'bg']), ('berg ', [' berg', ' bg', 'berg', 'bg'])] - def test_replace_suffix_decompose(self): repl = self.get_replacements("~berg => bg") assert repl == [(' berg ', [' bg', 'bg']), ('berg ', [' bg', 'bg'])] - def test_add_prefix_no_compose(self): repl = self.get_replacements("hinter~ |-> hnt") assert repl == [(' hinter', [' hinter', ' hnt']), (' hinter ', [' hinter', ' hnt'])] - def test_replace_prefix_no_compose(self): repl = self.get_replacements("hinter~ |=> hnt") - assert repl == [(' hinter', [' hnt']), (' hinter ', [' hnt'])] - + assert repl == [(' hinter', [' hnt']), (' hinter ', [' hnt'])] def test_add_prefix_compose(self): repl = self.get_replacements("hinter~-> h") @@ -206,45 +197,38 @@ class TestGetReplacements: assert repl == [(' hinter', [' h', ' h ', ' hinter', ' hinter ']), (' hinter ', [' h', ' h', ' hinter', ' hinter'])] - def test_replace_prefix_compose(self): repl = self.get_replacements("hinter~=> h") assert repl == [(' hinter', [' h', ' h ']), (' hinter ', [' h', ' h'])] - def test_add_beginning_only(self): repl = self.get_replacements("^Premier -> Pr") assert repl == [('^ premier ', ['^ pr', '^ premier'])] - def test_replace_beginning_only(self): repl = self.get_replacements("^Premier => Pr") assert repl == [('^ premier ', ['^ pr'])] - def test_add_final_only(self): repl = self.get_replacements("road$ -> rd") assert repl == [(' road ^', [' rd ^', ' road ^'])] - def test_replace_final_only(self): repl = self.get_replacements("road$ => rd") assert repl == [(' road ^', [' rd ^'])] - def test_decompose_only(self): repl = self.get_replacements("~foo -> foo") assert repl == [(' foo ', [' foo', 'foo']), ('foo ', [' foo', 'foo'])] - def test_add_suffix_decompose_end_only(self): repl = self.get_replacements("~berg |-> bg", "~berg$ -> bg") @@ -253,7 +237,6 @@ class TestGetReplacements: ('berg ', ['berg', 'bg']), ('berg ^', [' berg ^', ' bg ^', 'berg ^', 'bg ^'])] - def test_replace_suffix_decompose_end_only(self): repl = self.get_replacements("~berg |=> bg", "~berg$ => bg") @@ -262,7 +245,6 @@ class TestGetReplacements: ('berg ', ['bg']), ('berg ^', [' bg ^', 'bg ^'])] - @pytest.mark.parametrize('rule', ["~berg,~burg -> bg", "~berg, ~burg -> bg", "~berg,,~burg -> bg"]) diff --git a/test/python/tokenizer/token_analysis/test_generic_mutation.py b/test/python/tokenizer/token_analysis/test_generic_mutation.py index 7d0db925..2ce2236a 100644 --- a/test/python/tokenizer/token_analysis/test_generic_mutation.py +++ b/test/python/tokenizer/token_analysis/test_generic_mutation.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for generic token analysis, mutation part. @@ -24,37 +24,34 @@ DEFAULT_TRANSLITERATION = """ :: Latin (); '🜵' > ' '; """ + class TestMutationNoVariants: def make_analyser(self, *mutations): - rules = { 'analyzer': 'generic', - 'mutations': [ {'pattern': m[0], 'replacements': m[1]} - for m in mutations] - } + rules = {'analyzer': 'generic', + 'mutations': [{'pattern': m[0], 'replacements': m[1]} + for m in mutations] + } trans = Transliterator.createFromRules("test_trans", DEFAULT_TRANSLITERATION) norm = Transliterator.createFromRules("test_norm", DEFAULT_NORMALIZATION) config = module.configure(rules, norm, trans) self.analysis = module.create(norm, trans, config) - def variants(self, name): norm = Transliterator.createFromRules("test_norm", DEFAULT_NORMALIZATION) return set(self.analysis.compute_variants(norm.transliterate(name).strip())) - @pytest.mark.parametrize('pattern', ('(capture)', ['a list'])) def test_bad_pattern(self, pattern): with pytest.raises(UsageError): self.make_analyser((pattern, ['b'])) - @pytest.mark.parametrize('replacements', (None, 'a string')) def test_bad_replacement(self, replacements): with pytest.raises(UsageError): self.make_analyser(('a', replacements)) - def test_simple_replacement(self): self.make_analyser(('a', ['b'])) @@ -62,27 +59,23 @@ class TestMutationNoVariants: assert self.variants('abba') == {'bbbb'} assert self.variants('2 aar') == {'2 bbr'} - def test_multichar_replacement(self): self.make_analyser(('1 1', ['1 1 1'])) assert self.variants('1 1456') == {'1 1 1456'} assert self.variants('1 1 1') == {'1 1 1 1'} - def test_removement_replacement(self): self.make_analyser((' ', [' ', ''])) assert self.variants('A 345') == {'a 345', 'a345'} assert self.variants('a g b') == {'a g b', 'ag b', 'a gb', 'agb'} - def test_regex_pattern(self): self.make_analyser(('[^a-z]+', ['XXX', ' '])) assert self.variants('a-34n12') == {'aXXXnXXX', 'aXXXn', 'a nXXX', 'a n'} - def test_multiple_mutations(self): self.make_analyser(('ä', ['ä', 'ae']), ('ö', ['ö', 'oe'])) diff --git a/test/python/tokenizer/token_analysis/test_simple_trie.py b/test/python/tokenizer/token_analysis/test_simple_trie.py index 0384a456..6ce66580 100644 --- a/test/python/tokenizer/token_analysis/test_simple_trie.py +++ b/test/python/tokenizer/token_analysis/test_simple_trie.py @@ -10,6 +10,7 @@ Tests for simplified trie structure. from nominatim_db.tokenizer.token_analysis.simple_trie import SimpleTrie + def test_single_item_trie(): t = SimpleTrie([('foob', 42)]) @@ -18,6 +19,7 @@ def test_single_item_trie(): assert t.longest_prefix('foob') == (42, 4) assert t.longest_prefix('123foofoo', 3) == (None, 3) + def test_complex_item_tree(): t = SimpleTrie([('a', 1), ('b', 2), diff --git a/test/python/tools/conftest.py b/test/python/tools/conftest.py index 0098747e..dc9346c8 100644 --- a/test/python/tools/conftest.py +++ b/test/python/tools/conftest.py @@ -2,10 +2,11 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. import pytest + @pytest.fixture def osm2pgsql_options(temp_db, tmp_path): """ A standard set of options for osm2pgsql diff --git a/test/python/tools/test_add_osm_data.py b/test/python/tools/test_add_osm_data.py index c5aaaaae..38cf87c4 100644 --- a/test/python/tools/test_add_osm_data.py +++ b/test/python/tools/test_add_osm_data.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for functions to add additional data to the database. @@ -13,6 +13,7 @@ import pytest from nominatim_db.tools import add_osm_data + class CaptureGetUrl: def __init__(self, monkeypatch): @@ -29,6 +30,7 @@ def setup_delete_postprocessing(temp_db_cursor): temp_db_cursor.execute("""CREATE OR REPLACE FUNCTION flush_deleted_places() RETURNS INTEGER AS $$ SELECT 1 $$ LANGUAGE SQL""") + def test_import_osm_file_simple(dsn, table_factory, osm2pgsql_options, capfd): assert add_osm_data.add_data_from_file(dsn, Path('change.osm'), osm2pgsql_options) == 0 diff --git a/test/python/tools/test_admin.py b/test/python/tools/test_admin.py index 1e1f0e29..e758bca2 100644 --- a/test/python/tools/test_admin.py +++ b/test/python/tools/test_admin.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for maintenance and analysis functions. @@ -14,6 +14,7 @@ from nominatim_db.tools import admin from nominatim_db.tokenizer import factory from nominatim_db.db.sql_preprocessor import SQLPreprocessor + @pytest.fixture(autouse=True) def create_placex_table(project_env, tokenizer_mock, temp_db_cursor, placex_table): """ All tests in this module require the placex table to be set up. @@ -76,7 +77,8 @@ def test_analyse_indexing_with_osm_id(project_env, temp_db_cursor): class TestAdminCleanDeleted: @pytest.fixture(autouse=True) - def setup_polygon_delete(self, project_env, table_factory, place_table, osmline_table, temp_db_cursor, temp_db_conn, def_config, src_dir): + def setup_polygon_delete(self, project_env, table_factory, place_table, + osmline_table, temp_db_cursor, temp_db_conn, def_config, src_dir): """ Set up place_force_delete function and related tables """ self.project_env = project_env @@ -87,12 +89,14 @@ class TestAdminCleanDeleted: class TEXT NOT NULL, type TEXT NOT NULL""", ((100, 'N', 'boundary', 'administrative'), - (145, 'N', 'boundary', 'administrative'), - (175, 'R', 'landcover', 'grass'))) - temp_db_cursor.execute("""INSERT INTO placex (place_id, osm_id, osm_type, class, type, indexed_date, indexed_status) - VALUES(1, 100, 'N', 'boundary', 'administrative', current_date - INTERVAL '1 month', 1), - (2, 145, 'N', 'boundary', 'administrative', current_date - INTERVAL '3 month', 1), - (3, 175, 'R', 'landcover', 'grass', current_date - INTERVAL '3 months', 1)""") + (145, 'N', 'boundary', 'administrative'), + (175, 'R', 'landcover', 'grass'))) + temp_db_cursor.execute(""" + INSERT INTO placex (place_id, osm_id, osm_type, class, type, + indexed_date, indexed_status) + VALUES(1, 100, 'N', 'boundary', 'administrative', current_date - INTERVAL '1 month', 1), + (2, 145, 'N', 'boundary', 'administrative', current_date - INTERVAL '3 month', 1), + (3, 175, 'R', 'landcover', 'grass', current_date - INTERVAL '3 months', 1)""") # set up tables and triggers for utils function table_factory('place_to_be_deleted', """osm_id BIGINT, @@ -116,33 +120,42 @@ class TestAdminCleanDeleted: sqlproc = SQLPreprocessor(temp_db_conn, def_config) sqlproc.run_sql_file(temp_db_conn, 'functions/utils.sql') def_config.lib_dir.sql = orig_sql - def test_admin_clean_deleted_no_records(self): admin.clean_deleted_relations(self.project_env, age='1 year') - assert self.temp_db_cursor.row_set('SELECT osm_id, osm_type, class, type, indexed_status FROM placex') == {(100, 'N', 'boundary', 'administrative', 1), - (145, 'N', 'boundary', 'administrative', 1), - (175, 'R', 'landcover', 'grass', 1)} - assert self.temp_db_cursor.table_rows('import_polygon_delete') == 3 + rowset = self.temp_db_cursor.row_set( + 'SELECT osm_id, osm_type, class, type, indexed_status FROM placex') + + assert rowset == {(100, 'N', 'boundary', 'administrative', 1), + (145, 'N', 'boundary', 'administrative', 1), + (175, 'R', 'landcover', 'grass', 1)} + assert self.temp_db_cursor.table_rows('import_polygon_delete') == 3 @pytest.mark.parametrize('test_age', ['T week', '1 welk', 'P1E']) def test_admin_clean_deleted_bad_age(self, test_age): with pytest.raises(UsageError): - admin.clean_deleted_relations(self.project_env, age = test_age) - + admin.clean_deleted_relations(self.project_env, age=test_age) def test_admin_clean_deleted_partial(self): - admin.clean_deleted_relations(self.project_env, age = '2 months') - assert self.temp_db_cursor.row_set('SELECT osm_id, osm_type, class, type, indexed_status FROM placex') == {(100, 'N', 'boundary', 'administrative', 1), - (145, 'N', 'boundary', 'administrative', 100), - (175, 'R', 'landcover', 'grass', 100)} + admin.clean_deleted_relations(self.project_env, age='2 months') + + rowset = self.temp_db_cursor.row_set( + 'SELECT osm_id, osm_type, class, type, indexed_status FROM placex') + + assert rowset == {(100, 'N', 'boundary', 'administrative', 1), + (145, 'N', 'boundary', 'administrative', 100), + (175, 'R', 'landcover', 'grass', 100)} assert self.temp_db_cursor.table_rows('import_polygon_delete') == 1 @pytest.mark.parametrize('test_age', ['1 week', 'P3D', '5 hours']) def test_admin_clean_deleted(self, test_age): - admin.clean_deleted_relations(self.project_env, age = test_age) - assert self.temp_db_cursor.row_set('SELECT osm_id, osm_type, class, type, indexed_status FROM placex') == {(100, 'N', 'boundary', 'administrative', 100), - (145, 'N', 'boundary', 'administrative', 100), - (175, 'R', 'landcover', 'grass', 100)} + admin.clean_deleted_relations(self.project_env, age=test_age) + + rowset = self.temp_db_cursor.row_set( + 'SELECT osm_id, osm_type, class, type, indexed_status FROM placex') + + assert rowset == {(100, 'N', 'boundary', 'administrative', 100), + (145, 'N', 'boundary', 'administrative', 100), + (175, 'R', 'landcover', 'grass', 100)} assert self.temp_db_cursor.table_rows('import_polygon_delete') == 0 diff --git a/test/python/tools/test_check_database.py b/test/python/tools/test_check_database.py index 886bd75b..66506f56 100644 --- a/test/python/tools/test_check_database.py +++ b/test/python/tools/test_check_database.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for database integrity checks. @@ -12,6 +12,7 @@ import pytest from nominatim_db.tools import check_database as chkdb import nominatim_db.version + def test_check_database_unknown_db(def_config, monkeypatch): monkeypatch.setenv('NOMINATIM_DATABASE_DSN', 'pgsql:dbname=fjgkhughwgh2423gsags') assert chkdb.check_database(def_config) == 1 @@ -35,6 +36,7 @@ def test_check_database_version_good(property_table, temp_db_conn, def_config): str(nominatim_db.version.NOMINATIM_VERSION)) assert chkdb.check_database_version(temp_db_conn, def_config) == chkdb.CheckState.OK + def test_check_database_version_bad(property_table, temp_db_conn, def_config): property_table.set('database_version', '3.9.9-9') assert chkdb.check_database_version(temp_db_conn, def_config) == chkdb.CheckState.FATAL diff --git a/test/python/tools/test_database_import.py b/test/python/tools/test_database_import.py index df204298..f8cea2cc 100644 --- a/test/python/tools/test_database_import.py +++ b/test/python/tools/test_database_import.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for functions to import a new database. @@ -10,13 +10,14 @@ Tests for functions to import a new database. from pathlib import Path import pytest -import pytest_asyncio +import pytest_asyncio # noqa import psycopg from psycopg import sql as pysql from nominatim_db.tools import database_import from nominatim_db.errors import UsageError + class TestDatabaseSetup: DBNAME = 'test_nominatim_python_unittest' @@ -31,18 +32,15 @@ class TestDatabaseSetup: with conn.cursor() as cur: cur.execute(f'DROP DATABASE IF EXISTS {self.DBNAME}') - @pytest.fixture def cursor(self): with psycopg.connect(dbname=self.DBNAME) as conn: with conn.cursor() as cur: yield cur - def conn(self): return psycopg.connect(dbname=self.DBNAME) - def test_setup_skeleton(self): database_import.setup_database_skeleton(f'dbname={self.DBNAME}') @@ -51,25 +49,21 @@ class TestDatabaseSetup: with conn.cursor() as cur: cur.execute('CREATE TABLE t (h HSTORE, geom GEOMETRY(Geometry, 4326))') - def test_unsupported_pg_version(self, monkeypatch): monkeypatch.setattr(database_import, 'POSTGRESQL_REQUIRED_VERSION', (100, 4)) with pytest.raises(UsageError, match='PostgreSQL server is too old.'): database_import.setup_database_skeleton(f'dbname={self.DBNAME}') - def test_create_db_explicit_ro_user(self): database_import.setup_database_skeleton(f'dbname={self.DBNAME}', rouser='postgres') - def test_create_db_missing_ro_user(self): with pytest.raises(UsageError, match='Missing read-only user.'): database_import.setup_database_skeleton(f'dbname={self.DBNAME}', rouser='sdfwkjkjgdugu2;jgsafkljas;') - def test_setup_extensions_old_postgis(self, monkeypatch): monkeypatch.setattr(database_import, 'POSTGIS_REQUIRED_VERSION', (50, 50)) @@ -173,7 +167,7 @@ def test_truncate_database_tables(temp_db_conn, temp_db_cursor, table_factory, w @pytest.mark.parametrize("threads", (1, 5)) @pytest.mark.asyncio async def test_load_data(dsn, place_row, placex_table, osmline_table, - temp_db_cursor, threads): + temp_db_cursor, threads): for func in ('precompute_words', 'getorcreate_housenumber_id', 'make_standard_name'): temp_db_cursor.execute(pysql.SQL("""CREATE FUNCTION {} (src TEXT) RETURNS TEXT AS $$ SELECT 'a'::TEXT $$ LANGUAGE SQL @@ -198,11 +192,9 @@ class TestSetupSQL: self.config = def_config - def write_sql(self, fname, content): (self.config.lib_dir.sql / fname).write_text(content) - @pytest.mark.parametrize("reverse", [True, False]) def test_create_tables(self, temp_db_conn, temp_db_cursor, reverse): self.write_sql('tables.sql', @@ -213,7 +205,6 @@ class TestSetupSQL: temp_db_cursor.scalar('SELECT test()') == reverse - def test_create_table_triggers(self, temp_db_conn, temp_db_cursor): self.write_sql('table-triggers.sql', """CREATE FUNCTION test() RETURNS TEXT @@ -223,7 +214,6 @@ class TestSetupSQL: temp_db_cursor.scalar('SELECT test()') == 'a' - def test_create_partition_tables(self, temp_db_conn, temp_db_cursor): self.write_sql('partition-tables.src.sql', """CREATE FUNCTION test() RETURNS TEXT @@ -233,7 +223,6 @@ class TestSetupSQL: temp_db_cursor.scalar('SELECT test()') == 'b' - @pytest.mark.parametrize("drop", [True, False]) @pytest.mark.asyncio async def test_create_search_indices(self, temp_db_conn, temp_db_cursor, drop): diff --git a/test/python/tools/test_exec_utils.py b/test/python/tools/test_exec_utils.py index 666ef0b8..216f1a40 100644 --- a/test/python/tools/test_exec_utils.py +++ b/test/python/tools/test_exec_utils.py @@ -2,19 +2,14 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for tools.exec_utils module. """ -from pathlib import Path -import subprocess - -import pytest - -from nominatim_db.config import Configuration import nominatim_db.tools.exec_utils as exec_utils + def test_run_osm2pgsql(osm2pgsql_options): osm2pgsql_options['append'] = False osm2pgsql_options['import_file'] = 'foo.bar' diff --git a/test/python/tools/test_freeze.py b/test/python/tools/test_freeze.py index f64850fb..21e49b8d 100644 --- a/test/python/tools/test_freeze.py +++ b/test/python/tools/test_freeze.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for freeze functions (removing unused database parts). @@ -26,6 +26,7 @@ NOMINATIM_DROP_TABLES = [ 'wikipedia_article', 'wikipedia_redirect' ] + def test_drop_tables(temp_db_conn, temp_db_cursor, table_factory): for table in NOMINATIM_RUNTIME_TABLES + NOMINATIM_DROP_TABLES: table_factory(table) @@ -42,6 +43,7 @@ def test_drop_tables(temp_db_conn, temp_db_cursor, table_factory): assert freeze.is_frozen(temp_db_conn) + def test_drop_flatnode_file_no_file(): freeze.drop_flatnode_file(None) diff --git a/test/python/tools/test_import_special_phrases.py b/test/python/tools/test_import_special_phrases.py index 0d33e6e0..d8fe8946 100644 --- a/test/python/tools/test_import_special_phrases.py +++ b/test/python/tools/test_import_special_phrases.py @@ -2,20 +2,17 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for import special phrases methods of the class SPImporter. """ -from shutil import copyfile import pytest from nominatim_db.tools.special_phrases.sp_importer import SPImporter from nominatim_db.tools.special_phrases.sp_wiki_loader import SPWikiLoader from nominatim_db.tools.special_phrases.special_phrase import SpecialPhrase -from nominatim_db.errors import UsageError -from cursor import CursorForTesting @pytest.fixture def sp_importer(temp_db_conn, def_config, monkeypatch): @@ -53,6 +50,7 @@ def test_fetch_existing_place_classtype_tables(sp_importer, table_factory): contained_table = sp_importer.table_phrases_to_delete.pop() assert contained_table == 'place_classtype_testclasstypetable' + def test_check_sanity_class(sp_importer): """ Check for _check_sanity() method. @@ -65,6 +63,7 @@ def test_check_sanity_class(sp_importer): assert sp_importer._check_sanity(SpecialPhrase('en', 'class', 'type', '')) + def test_load_white_and_black_lists(sp_importer): """ Test that _load_white_and_black_lists() well return @@ -93,6 +92,7 @@ def test_create_place_classtype_indexes(temp_db_with_extensions, assert check_placeid_and_centroid_indexes(temp_db_cursor, phrase_class, phrase_type) + def test_create_place_classtype_table(temp_db_conn, temp_db_cursor, placex_table, sp_importer): """ Test that _create_place_classtype_table() create @@ -105,6 +105,7 @@ def test_create_place_classtype_table(temp_db_conn, temp_db_cursor, placex_table assert check_table_exist(temp_db_cursor, phrase_class, phrase_type) + def test_grant_access_to_web_user(temp_db_conn, temp_db_cursor, table_factory, def_config, sp_importer): """ @@ -120,7 +121,9 @@ def test_grant_access_to_web_user(temp_db_conn, temp_db_cursor, table_factory, sp_importer._grant_access_to_webuser(phrase_class, phrase_type) temp_db_conn.commit() - assert check_grant_access(temp_db_cursor, def_config.DATABASE_WEBUSER, phrase_class, phrase_type) + assert check_grant_access(temp_db_cursor, def_config.DATABASE_WEBUSER, + phrase_class, phrase_type) + def test_create_place_classtype_table_and_indexes( temp_db_cursor, def_config, placex_table, @@ -141,6 +144,7 @@ def test_create_place_classtype_table_and_indexes( assert check_placeid_and_centroid_indexes(temp_db_cursor, pair[0], pair[1]) assert check_grant_access(temp_db_cursor, def_config.DATABASE_WEBUSER, pair[0], pair[1]) + def test_remove_non_existent_tables_from_db(sp_importer, default_phrases, temp_db_conn, temp_db_cursor): """ @@ -168,7 +172,7 @@ def test_remove_non_existent_tables_from_db(sp_importer, default_phrases, temp_db_conn.commit() assert temp_db_cursor.row_set(query_tables) \ - == {('place_classtype_testclasstypetable_to_keep', )} + == {('place_classtype_testclasstypetable_to_keep', )} @pytest.mark.parametrize("should_replace", [(True), (False)]) @@ -182,8 +186,8 @@ def test_import_phrases(monkeypatch, temp_db_cursor, def_config, sp_importer, It should also update the database well by deleting or preserving existing entries of the database. """ - #Add some data to the database before execution in order to test - #what is deleted and what is preserved. + # Add some data to the database before execution in order to test + # what is deleted and what is preserved. table_factory('place_classtype_amenity_animal_shelter') table_factory('place_classtype_wrongclass_wrongtype') @@ -209,6 +213,7 @@ def test_import_phrases(monkeypatch, temp_db_cursor, def_config, sp_importer, if should_replace: assert not temp_db_cursor.table_exists('place_classtype_wrongclass_wrongtype') + def check_table_exist(temp_db_cursor, phrase_class, phrase_type): """ Verify that the place_classtype table exists for the given @@ -231,6 +236,7 @@ def check_grant_access(temp_db_cursor, user, phrase_class, phrase_type): AND privilege_type='SELECT'""".format(table_name, user)) return temp_db_cursor.fetchone() + def check_placeid_and_centroid_indexes(temp_db_cursor, phrase_class, phrase_type): """ Check that the place_id index and centroid index exist for the diff --git a/test/python/tools/test_migration.py b/test/python/tools/test_migration.py index 0b4d2ec6..00f6a7d7 100644 --- a/test/python/tools/test_migration.py +++ b/test/python/tools/test_migration.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for migration functions @@ -11,9 +11,9 @@ import pytest from nominatim_db.tools import migration from nominatim_db.errors import UsageError -from nominatim_db.db.connection import server_version_tuple import nominatim_db.version + class DummyTokenizer: def update_sql_functions(self, config): @@ -49,6 +49,7 @@ def test_run_single_migration(temp_db_with_extensions, def_config, temp_db_curso str(nominatim_db.version.NominatimVersion(*oldversion))) done = {'old': False, 'new': False} + def _migration(**_): """ Dummy migration""" done['new'] = True @@ -69,7 +70,7 @@ def test_run_single_migration(temp_db_with_extensions, def_config, temp_db_curso assert property_table.get('database_version') == str(nominatim_db.version.NOMINATIM_VERSION) -###### Tests for specific migrations +# Tests for specific migrations # # Each migration should come with two tests: # 1. Test that migration from old to new state works as expected. diff --git a/test/python/tools/test_postcodes.py b/test/python/tools/test_postcodes.py index f035bb19..b03c9748 100644 --- a/test/python/tools/test_postcodes.py +++ b/test/python/tools/test_postcodes.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for functions to maintain the artificial postcode table. @@ -15,6 +15,7 @@ from nominatim_db.tools import postcodes from nominatim_db.data import country_info import dummy_tokenizer + class MockPostcodeTable: """ A location_postcode table for testing. """ @@ -35,7 +36,7 @@ class MockPostcodeTable: RETURNS TEXT AS $$ BEGIN RETURN postcode; END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION get_country_code(place geometry) - RETURNS TEXT AS $$ BEGIN + RETURNS TEXT AS $$ BEGIN RETURN null; END; $$ LANGUAGE plpgsql; """) @@ -51,7 +52,6 @@ class MockPostcodeTable: (country, postcode, x, y)) self.conn.commit() - @property def row_set(self): with self.conn.cursor() as cur: @@ -180,7 +180,7 @@ def test_postcodes_extern(dsn, postcode_table, tmp_path, ('xx', 'CD 4511', -10, -5)} -def test_postcodes_extern_bad_column(dsn, postcode_table, tmp_path, +def test_postcodes_extern_bad_column(dsn, postcode_table, tmp_path, insert_implicit_postcode, tokenizer): insert_implicit_postcode(1, 'xx', 'POINT(10 12)', dict(postcode='AB 4511')) @@ -204,6 +204,7 @@ def test_postcodes_extern_bad_number(dsn, insert_implicit_postcode, assert postcode_table.row_set == {('xx', 'AB 4511', 10, 12), ('xx', 'CD 4511', -10, -5)} + def test_can_compute(dsn, table_factory): assert not postcodes.can_compute(dsn) table_factory('place') @@ -211,10 +212,10 @@ def test_can_compute(dsn, table_factory): def test_no_placex_entry(dsn, tmp_path, temp_db_cursor, place_row, postcode_table, tokenizer): - #Rewrite the get_country_code function to verify its execution. + # Rewrite the get_country_code function to verify its execution. temp_db_cursor.execute(""" CREATE OR REPLACE FUNCTION get_country_code(place geometry) - RETURNS TEXT AS $$ BEGIN + RETURNS TEXT AS $$ BEGIN RETURN 'yy'; END; $$ LANGUAGE plpgsql; """) @@ -224,11 +225,12 @@ def test_no_placex_entry(dsn, tmp_path, temp_db_cursor, place_row, postcode_tabl assert postcode_table.row_set == {('yy', 'AB 4511', 10, 12)} -def test_discard_badly_formatted_postcodes(dsn, tmp_path, temp_db_cursor, place_row, postcode_table, tokenizer): - #Rewrite the get_country_code function to verify its execution. +def test_discard_badly_formatted_postcodes(dsn, tmp_path, temp_db_cursor, place_row, + postcode_table, tokenizer): + # Rewrite the get_country_code function to verify its execution. temp_db_cursor.execute(""" CREATE OR REPLACE FUNCTION get_country_code(place geometry) - RETURNS TEXT AS $$ BEGIN + RETURNS TEXT AS $$ BEGIN RETURN 'fr'; END; $$ LANGUAGE plpgsql; """) diff --git a/test/python/tools/test_refresh.py b/test/python/tools/test_refresh.py index 8f735180..95feef0d 100644 --- a/test/python/tools/test_refresh.py +++ b/test/python/tools/test_refresh.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Test for various refresh functions. @@ -12,7 +12,7 @@ from pathlib import Path import pytest from nominatim_db.tools import refresh -from nominatim_db.db.connection import postgis_version_tuple + def test_refresh_import_wikipedia_not_existing(dsn): assert refresh.import_wikipedia_articles(dsn, Path('.')) == 1 @@ -21,6 +21,7 @@ def test_refresh_import_wikipedia_not_existing(dsn): def test_refresh_import_secondary_importance_non_existing(dsn): assert refresh.import_secondary_importance(dsn, Path('.')) == 1 + def test_refresh_import_secondary_importance_testdb(dsn, src_dir, temp_db_conn, temp_db_cursor): temp_db_cursor.execute('CREATE EXTENSION postgis') temp_db_cursor.execute('CREATE EXTENSION postgis_raster') diff --git a/test/python/tools/test_refresh_address_levels.py b/test/python/tools/test_refresh_address_levels.py index 6e094cdc..f2bfdea6 100644 --- a/test/python/tools/test_refresh_address_levels.py +++ b/test/python/tools/test_refresh_address_levels.py @@ -2,23 +2,24 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for function for importing address ranks. """ import json -from pathlib import Path import pytest from nominatim_db.tools.refresh import load_address_levels, load_address_levels_from_config + def test_load_ranks_def_config(temp_db_conn, temp_db_cursor, def_config): load_address_levels_from_config(temp_db_conn, def_config) assert temp_db_cursor.table_rows('address_levels') > 0 + def test_load_ranks_from_project_dir(project_env, temp_db_conn, temp_db_cursor): test_file = project_env.project_dir / 'address-levels.json' test_file.write_text('[{"tags":{"place":{"sea":2}}}]') @@ -43,14 +44,14 @@ def test_load_ranks_country(temp_db_conn, temp_db_cursor): "tags": {"place": {"village": 15}}}, {"countries": ['uk', 'us'], "tags": {"place": {"village": 16}}} - ]) + ]) assert temp_db_cursor.row_set('SELECT * FROM levels') == \ set([(None, 'place', 'village', 14, 14), ('de', 'place', 'village', 15, 15), ('uk', 'place', 'village', 16, 16), ('us', 'place', 'village', 16, 16), - ]) + ]) def test_load_ranks_default_value(temp_db_conn, temp_db_cursor): @@ -58,33 +59,33 @@ def test_load_ranks_default_value(temp_db_conn, temp_db_cursor): [{"tags": {"boundary": {"": 28}}}, {"countries": ['hu'], "tags": {"boundary": {"": 29}}} - ]) + ]) assert temp_db_cursor.row_set('SELECT * FROM levels') == \ set([(None, 'boundary', None, 28, 28), ('hu', 'boundary', None, 29, 29), - ]) + ]) def test_load_ranks_multiple_keys(temp_db_conn, temp_db_cursor): load_address_levels(temp_db_conn, 'levels', [{"tags": {"place": {"city": 14}, - "boundary": {"administrative2" : 4}} - }]) + "boundary": {"administrative2": 4}} + }]) assert temp_db_cursor.row_set('SELECT * FROM levels') == \ set([(None, 'place', 'city', 14, 14), (None, 'boundary', 'administrative2', 4, 4), - ]) + ]) def test_load_ranks_address(temp_db_conn, temp_db_cursor): load_address_levels(temp_db_conn, 'levels', [{"tags": {"place": {"city": 14, - "town" : [14, 13]}} - }]) + "town": [14, 13]}} + }]) assert temp_db_cursor.row_set('SELECT * FROM levels') == \ set([(None, 'place', 'city', 14, 14), (None, 'place', 'town', 14, 13), - ]) + ]) diff --git a/test/python/tools/test_refresh_create_functions.py b/test/python/tools/test_refresh_create_functions.py index 984a1610..bd8724d6 100644 --- a/test/python/tools/test_refresh_create_functions.py +++ b/test/python/tools/test_refresh_create_functions.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for creating PL/pgSQL functions for Nominatim. @@ -11,6 +11,7 @@ import pytest from nominatim_db.tools.refresh import create_functions + class TestCreateFunctions: @pytest.fixture(autouse=True) def init_env(self, sql_preprocessor, temp_db_conn, def_config, tmp_path): @@ -18,12 +19,10 @@ class TestCreateFunctions: self.config = def_config def_config.lib_dir.sql = tmp_path - def write_functions(self, content): sqlfile = self.config.lib_dir.sql / 'functions.sql' sqlfile.write_text(content) - def test_create_functions(self, temp_db_cursor): self.write_functions("""CREATE OR REPLACE FUNCTION test() RETURNS INTEGER AS $$ @@ -37,7 +36,6 @@ class TestCreateFunctions: assert temp_db_cursor.scalar('SELECT test()') == 43 - @pytest.mark.parametrize("dbg,ret", ((True, 43), (False, 22))) def test_create_functions_with_template(self, temp_db_cursor, dbg, ret): self.write_functions("""CREATE OR REPLACE FUNCTION test() RETURNS INTEGER diff --git a/test/python/tools/test_refresh_wiki_data.py b/test/python/tools/test_refresh_wiki_data.py index 997ba04d..046e9191 100644 --- a/test/python/tools/test_refresh_wiki_data.py +++ b/test/python/tools/test_refresh_wiki_data.py @@ -12,7 +12,10 @@ import csv import pytest -from nominatim_db.tools.refresh import import_wikipedia_articles, recompute_importance, create_functions +from nominatim_db.tools.refresh import (import_wikipedia_articles, + recompute_importance, + create_functions) + @pytest.fixture def wiki_csv(tmp_path, sql_preprocessor): @@ -25,7 +28,7 @@ def wiki_csv(tmp_path, sql_preprocessor): for lang, title, importance, wd in data: writer.writerow({'language': lang, 'type': 'a', 'title': title, 'importance': str(importance), - 'wikidata_id' : wd}) + 'wikidata_id': wd}) return tmp_path return _import diff --git a/test/python/tools/test_replication.py b/test/python/tools/test_replication.py index 392ea075..347899bd 100644 --- a/test/python/tools/test_replication.py +++ b/test/python/tools/test_replication.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for replication functionality. @@ -22,13 +22,15 @@ OSM_NODE_DATA = """\ -""" +""" # noqa + @pytest.fixture(autouse=True) def setup_status_table(status_table): pass -### init replication + +# init replication def test_init_replication_bad_base_url(monkeypatch, place_row, temp_db_conn): place_row(osm_type='N', osm_id=100) @@ -50,13 +52,13 @@ def test_init_replication_success(monkeypatch, place_row, temp_db_conn, temp_db_ nominatim_db.tools.replication.init_replication(temp_db_conn, 'https://test.io') expected_date = dt.datetime.strptime('2006-01-27T19:09:10', status.ISODATE_FORMAT)\ - .replace(tzinfo=dt.timezone.utc) + .replace(tzinfo=dt.timezone.utc) assert temp_db_cursor.row_set("SELECT * FROM import_status") \ - == {(expected_date, 234, True)} + == {(expected_date, 234, True)} -### checking for updates +# checking for updates def test_check_for_updates_empty_status_table(temp_db_conn): assert nominatim_db.tools.replication.check_for_updates(temp_db_conn, 'https://test.io') == 254 @@ -87,10 +89,11 @@ def test_check_for_updates_no_new_data(monkeypatch, temp_db_conn, "get_state_info", lambda self: OsmosisState(server_sequence, date)) - assert nominatim_db.tools.replication.check_for_updates(temp_db_conn, 'https://test.io') == result + assert result == \ + nominatim_db.tools.replication.check_for_updates(temp_db_conn, 'https://test.io') -### updating +# updating @pytest.fixture def update_options(tmpdir): @@ -100,6 +103,7 @@ def update_options(tmpdir): import_file=tmpdir / 'foo.osm', max_diff_size=1) + def test_update_empty_status_table(dsn): with pytest.raises(UsageError): nominatim_db.tools.replication.update(dsn, {}) @@ -109,7 +113,7 @@ def test_update_already_indexed(temp_db_conn, dsn): status.set_status(temp_db_conn, dt.datetime.now(dt.timezone.utc), seq=34, indexed=False) assert nominatim_db.tools.replication.update(dsn, dict(indexed_only=True)) \ - == nominatim_db.tools.replication.UpdateState.MORE_PENDING + == nominatim_db.tools.replication.UpdateState.MORE_PENDING def test_update_no_data_no_sleep(monkeypatch, temp_db_conn, dsn, update_options): @@ -124,7 +128,7 @@ def test_update_no_data_no_sleep(monkeypatch, temp_db_conn, dsn, update_options) monkeypatch.setattr(time, 'sleep', sleeptime.append) assert nominatim_db.tools.replication.update(dsn, update_options) \ - == nominatim_db.tools.replication.UpdateState.NO_CHANGES + == nominatim_db.tools.replication.UpdateState.NO_CHANGES assert not sleeptime @@ -141,7 +145,7 @@ def test_update_no_data_sleep(monkeypatch, temp_db_conn, dsn, update_options): monkeypatch.setattr(time, 'sleep', sleeptime.append) assert nominatim_db.tools.replication.update(dsn, update_options) \ - == nominatim_db.tools.replication.UpdateState.NO_CHANGES + == nominatim_db.tools.replication.UpdateState.NO_CHANGES assert len(sleeptime) == 1 assert sleeptime[0] < 3600 diff --git a/test/python/tools/test_sp_csv_loader.py b/test/python/tools/test_sp_csv_loader.py index 9d0ad9cc..67d6eed5 100644 --- a/test/python/tools/test_sp_csv_loader.py +++ b/test/python/tools/test_sp_csv_loader.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for methods of the SPCsvLoader class. @@ -13,6 +13,7 @@ from nominatim_db.errors import UsageError from nominatim_db.tools.special_phrases.sp_csv_loader import SPCsvLoader from nominatim_db.tools.special_phrases.special_phrase import SpecialPhrase + @pytest.fixture def sp_csv_loader(src_dir): """ diff --git a/test/python/tools/test_sp_wiki_loader.py b/test/python/tools/test_sp_wiki_loader.py index 5c37c32f..b8e41cbe 100644 --- a/test/python/tools/test_sp_wiki_loader.py +++ b/test/python/tools/test_sp_wiki_loader.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for methods of the SPWikiLoader class. @@ -36,22 +36,22 @@ def test_generate_phrases(sp_wiki_loader): """ phrases = list(sp_wiki_loader.generate_phrases()) - assert set((p.p_label, p.p_class, p.p_type, p.p_operator) for p in phrases) ==\ - {('Zip Line', 'aerialway', 'zip_line', '-'), - ('Zip Lines', 'aerialway', 'zip_line', '-'), - ('Zip Line in', 'aerialway', 'zip_line', 'in'), - ('Zip Lines in', 'aerialway', 'zip_line', 'in'), - ('Zip Line near', 'aerialway', 'zip_line', 'near'), - ('Animal shelter', 'amenity', 'animal_shelter', '-'), - ('Animal shelters', 'amenity', 'animal_shelter', '-'), - ('Animal shelter in', 'amenity', 'animal_shelter', 'in'), - ('Animal shelters in', 'amenity', 'animal_shelter', 'in'), - ('Animal shelter near', 'amenity', 'animal_shelter', 'near'), - ('Animal shelters near', 'amenity', 'animal_shelter', 'near'), - ('Drinking Water near', 'amenity', 'drinking_water', 'near'), - ('Water', 'amenity', 'drinking_water', '-'), - ('Water in', 'amenity', 'drinking_water', 'in'), - ('Water near', 'amenity', 'drinking_water', 'near'), - ('Embassy', 'amenity', 'embassy', '-'), - ('Embassys', 'amenity', 'embassy', '-'), - ('Embassies', 'amenity', 'embassy', '-')} + assert set((p.p_label, p.p_class, p.p_type, p.p_operator) for p in phrases) == \ + {('Zip Line', 'aerialway', 'zip_line', '-'), + ('Zip Lines', 'aerialway', 'zip_line', '-'), + ('Zip Line in', 'aerialway', 'zip_line', 'in'), + ('Zip Lines in', 'aerialway', 'zip_line', 'in'), + ('Zip Line near', 'aerialway', 'zip_line', 'near'), + ('Animal shelter', 'amenity', 'animal_shelter', '-'), + ('Animal shelters', 'amenity', 'animal_shelter', '-'), + ('Animal shelter in', 'amenity', 'animal_shelter', 'in'), + ('Animal shelters in', 'amenity', 'animal_shelter', 'in'), + ('Animal shelter near', 'amenity', 'animal_shelter', 'near'), + ('Animal shelters near', 'amenity', 'animal_shelter', 'near'), + ('Drinking Water near', 'amenity', 'drinking_water', 'near'), + ('Water', 'amenity', 'drinking_water', '-'), + ('Water in', 'amenity', 'drinking_water', 'in'), + ('Water near', 'amenity', 'drinking_water', 'near'), + ('Embassy', 'amenity', 'embassy', '-'), + ('Embassys', 'amenity', 'embassy', '-'), + ('Embassies', 'amenity', 'embassy', '-')} diff --git a/test/python/tools/test_tiger_data.py b/test/python/tools/test_tiger_data.py index 5d65fafb..f7dfe32e 100644 --- a/test/python/tools/test_tiger_data.py +++ b/test/python/tools/test_tiger_data.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Test for tiger data function @@ -11,12 +11,13 @@ import tarfile from textwrap import dedent import pytest -import pytest_asyncio +import pytest_asyncio # noqa: F401 from nominatim_db.db.connection import execute_scalar from nominatim_db.tools import tiger_data, freeze from nominatim_db.errors import UsageError + class MockTigerTable: def __init__(self, conn): @@ -40,6 +41,7 @@ class MockTigerTable: cur.execute("SELECT * FROM tiger LIMIT 1") return cur.fetchone() + @pytest.fixture def tiger_table(def_config, temp_db_conn, sql_preprocessor, temp_db_with_extensions, tmp_path): @@ -87,7 +89,7 @@ async def test_add_tiger_data(def_config, src_dir, tiger_table, tokenizer_mock, @pytest.mark.asyncio async def test_add_tiger_data_database_frozen(def_config, temp_db_conn, tiger_table, tokenizer_mock, - tmp_path): + tmp_path): freeze.drop_update_tables(temp_db_conn) with pytest.raises(UsageError) as excinfo: @@ -100,7 +102,7 @@ async def test_add_tiger_data_database_frozen(def_config, temp_db_conn, tiger_ta @pytest.mark.asyncio async def test_add_tiger_data_no_files(def_config, tiger_table, tokenizer_mock, - tmp_path): + tmp_path): await tiger_data.add_tiger_data(str(tmp_path), def_config, 1, tokenizer_mock()) assert tiger_table.count() == 0 @@ -108,7 +110,7 @@ async def test_add_tiger_data_no_files(def_config, tiger_table, tokenizer_mock, @pytest.mark.asyncio async def test_add_tiger_data_bad_file(def_config, tiger_table, tokenizer_mock, - tmp_path): + tmp_path): sqlfile = tmp_path / '1010.csv' sqlfile.write_text("""Random text""") @@ -119,7 +121,7 @@ async def test_add_tiger_data_bad_file(def_config, tiger_table, tokenizer_mock, @pytest.mark.asyncio async def test_add_tiger_data_hnr_nan(def_config, tiger_table, tokenizer_mock, - csv_factory, tmp_path): + csv_factory, tmp_path): csv_factory('file1', hnr_from=99) csv_factory('file2', hnr_from='L12') csv_factory('file3', hnr_to='12.4') @@ -133,7 +135,7 @@ async def test_add_tiger_data_hnr_nan(def_config, tiger_table, tokenizer_mock, @pytest.mark.parametrize("threads", (1, 5)) @pytest.mark.asyncio async def test_add_tiger_data_tarfile(def_config, tiger_table, tokenizer_mock, - tmp_path, src_dir, threads): + tmp_path, src_dir, threads): tar = tarfile.open(str(tmp_path / 'sample.tar.gz'), "w:gz") tar.add(str(src_dir / 'test' / 'testdb' / 'tiger' / '01001.csv')) tar.close() @@ -146,7 +148,7 @@ async def test_add_tiger_data_tarfile(def_config, tiger_table, tokenizer_mock, @pytest.mark.asyncio async def test_add_tiger_data_bad_tarfile(def_config, tiger_table, tokenizer_mock, - tmp_path): + tmp_path): tarfile = tmp_path / 'sample.tar.gz' tarfile.write_text("""Random text""") @@ -156,7 +158,7 @@ async def test_add_tiger_data_bad_tarfile(def_config, tiger_table, tokenizer_moc @pytest.mark.asyncio async def test_add_tiger_data_empty_tarfile(def_config, tiger_table, tokenizer_mock, - tmp_path): + tmp_path): tar = tarfile.open(str(tmp_path / 'sample.tar.gz'), "w:gz") tar.add(__file__) tar.close() diff --git a/test/python/utils/test_centroid.py b/test/python/utils/test_centroid.py index bac0edb3..664d5cd7 100644 --- a/test/python/utils/test_centroid.py +++ b/test/python/utils/test_centroid.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for centroid computation. @@ -11,6 +11,7 @@ import pytest from nominatim_db.utils.centroid import PointsCentroid + def test_empty_set(): c = PointsCentroid() @@ -18,7 +19,7 @@ def test_empty_set(): c.centroid() -@pytest.mark.parametrize("centroid", [(0,0), (-1, 3), [0.0000032, 88.4938]]) +@pytest.mark.parametrize("centroid", [(0, 0), (-1, 3), [0.0000032, 88.4938]]) def test_one_point_centroid(centroid): c = PointsCentroid() diff --git a/test/python/utils/test_json_writer.py b/test/python/utils/test_json_writer.py index 53e3f4d3..c0946f01 100644 --- a/test/python/utils/test_json_writer.py +++ b/test/python/utils/test_json_writer.py @@ -2,7 +2,7 @@ # # This file is part of Nominatim. (https://nominatim.org) # -# Copyright (C) 2024 by the Nominatim developer community. +# Copyright (C) 2025 by the Nominatim developer community. # For a full list of authors see the git log. """ Tests for the streaming JSON writer. @@ -13,6 +13,7 @@ import pytest from nominatim_api.utils.json_writer import JsonWriter + @pytest.mark.parametrize("inval,outstr", [(None, 'null'), (True, 'true'), (False, 'false'), (23, '23'), (0, '0'), (-1.3, '-1.3'), @@ -71,6 +72,7 @@ def test_object_single_entry(): assert writer() == '{"something":5}' json.loads(writer()) + def test_object_many_values(): writer = JsonWriter()\ .start_object()\ @@ -82,6 +84,7 @@ def test_object_many_values(): assert writer() == '{"foo":null,"bar":{},"baz":"b\\taz"}' json.loads(writer()) + def test_object_many_values_without_none(): writer = JsonWriter()\ .start_object()\ @@ -89,7 +92,7 @@ def test_object_many_values_without_none(): .keyval_not_none('bar', None)\ .keyval_not_none('baz', '')\ .keyval_not_none('eve', False, - transform = lambda v: 'yes' if v else 'no')\ + transform=lambda v: 'yes' if v else 'no')\ .end_object() assert writer() == '{"foo":0,"baz":"","eve":"no"}'