enable flake for Python tests

This commit is contained in:
Sarah Hoffmann
2025-03-09 15:33:24 +01:00
parent 5a245e33e0
commit 4cc788f69e
93 changed files with 949 additions and 1191 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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))

View File

@@ -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))

View File

@@ -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

View File

@@ -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')}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)]

View File

@@ -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',

View File

@@ -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,

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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'],

View File

@@ -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))

View File

@@ -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')

View File

@@ -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):

View File

@@ -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)

View File

@@ -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]]]

View File

@@ -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()

View File

@@ -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'}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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),

View File

@@ -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,

View File

@@ -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°2646″N 79°5856″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°2646″N 79°5856″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)

View File

@@ -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'])
assert l.display_name(None) == ''
assert l.display_name({}) == ''
def test_display_name_empty_names():
loc = Locales(['en', 'de'])
assert loc.display_name(None) == ''
assert loc.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',

View File

@@ -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': []}

View File

@@ -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)

View File

@@ -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'),

View File

@@ -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()

View File

@@ -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']])