forked from hans/Nominatim
add server fronting for search endpoint
This also implements some of the quirks of free-text search of the V1 API, in particular, search for categories and coordinates.
This commit is contained in:
112
test/python/api/test_helpers_v1.py
Normal file
112
test/python/api/test_helpers_v1.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# This file is part of Nominatim. (https://nominatim.org)
|
||||
#
|
||||
# Copyright (C) 2023 by the Nominatim developer community.
|
||||
# For a full list of authors see the git log.
|
||||
"""
|
||||
Tests for the helper functions for v1 API.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
import nominatim.api.v1.helpers as helper
|
||||
|
||||
@pytest.mark.parametrize('inp', ['', 'abc', '12 23', 'abc -78.90, 12.456 def'])
|
||||
def test_extract_coords_no_coords(inp):
|
||||
query, x, y = helper.extract_coords_from_query(inp)
|
||||
|
||||
assert query == inp
|
||||
assert x is None
|
||||
assert y is None
|
||||
|
||||
|
||||
def test_extract_coords_null_island():
|
||||
assert ('', 0.0, 0.0) == helper.extract_coords_from_query('0.0 -0.0')
|
||||
|
||||
|
||||
def test_extract_coords_with_text_before():
|
||||
assert ('abc', 12.456, -78.90) == helper.extract_coords_from_query('abc -78.90, 12.456')
|
||||
|
||||
|
||||
def test_extract_coords_with_text_after():
|
||||
assert ('abc', 12.456, -78.90) == helper.extract_coords_from_query('-78.90, 12.456 abc')
|
||||
|
||||
@pytest.mark.parametrize('inp', [' [12.456,-78.90] ', ' 12.456,-78.90 '])
|
||||
def test_extract_coords_with_spaces(inp):
|
||||
assert ('', -78.90, 12.456) == helper.extract_coords_from_query(inp)
|
||||
|
||||
@pytest.mark.parametrize('inp', ['40 26.767 N 79 58.933 W',
|
||||
'40° 26.767′ N 79° 58.933′ W',
|
||||
"40° 26.767' N 79° 58.933' W",
|
||||
"40° 26.767'\n"
|
||||
" N 79° 58.933' W",
|
||||
'N 40 26.767, W 79 58.933',
|
||||
'N 40°26.767′, W 79°58.933′',
|
||||
' N 40°26.767′, W 79°58.933′',
|
||||
"N 40°26.767', W 79°58.933'",
|
||||
|
||||
'40 26 46 N 79 58 56 W',
|
||||
'40° 26′ 46″ N 79° 58′ 56″ W',
|
||||
'40° 26′ 46.00″ N 79° 58′ 56.00″ W',
|
||||
'40°26′46″N 79°58′56″W',
|
||||
'N 40 26 46 W 79 58 56',
|
||||
'N 40° 26′ 46″, W 79° 58′ 56″',
|
||||
'N 40° 26\' 46", W 79° 58\' 56"',
|
||||
'N 40° 26\' 46", W 79° 58\' 56"',
|
||||
|
||||
'40.446 -79.982',
|
||||
'40.446,-79.982',
|
||||
'40.446° N 79.982° W',
|
||||
'N 40.446° W 79.982°',
|
||||
|
||||
'[40.446 -79.982]',
|
||||
'[40.446,-79.982]',
|
||||
' 40.446 , -79.982 ',
|
||||
' 40.446 , -79.982 ',
|
||||
' 40.446 , -79.982 ',
|
||||
' 40.446, -79.982 '])
|
||||
def test_extract_coords_formats(inp):
|
||||
query, x, y = helper.extract_coords_from_query(inp)
|
||||
|
||||
assert query == ''
|
||||
assert pytest.approx(x, abs=0.001) == -79.982
|
||||
assert pytest.approx(y, abs=0.001) == 40.446
|
||||
|
||||
query, x, y = helper.extract_coords_from_query('foo bar ' + inp)
|
||||
|
||||
assert query == 'foo bar'
|
||||
assert pytest.approx(x, abs=0.001) == -79.982
|
||||
assert pytest.approx(y, abs=0.001) == 40.446
|
||||
|
||||
query, x, y = helper.extract_coords_from_query(inp + ' x')
|
||||
|
||||
assert query == 'x'
|
||||
assert pytest.approx(x, abs=0.001) == -79.982
|
||||
assert pytest.approx(y, abs=0.001) == 40.446
|
||||
|
||||
|
||||
def test_extract_coords_formats_southeast():
|
||||
query, x, y = helper.extract_coords_from_query('S 40 26.767, E 79 58.933')
|
||||
|
||||
assert query == ''
|
||||
assert pytest.approx(x, abs=0.001) == 79.982
|
||||
assert pytest.approx(y, abs=0.001) == -40.446
|
||||
|
||||
|
||||
@pytest.mark.parametrize('inp', ['[shop=fish] foo bar',
|
||||
'foo [shop=fish] bar',
|
||||
'foo [shop=fish]bar',
|
||||
'foo bar [shop=fish]'])
|
||||
def test_extract_category_good(inp):
|
||||
query, cls, typ = helper.extract_category_from_query(inp)
|
||||
|
||||
assert query == 'foo bar'
|
||||
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):
|
||||
assert helper.extract_category_from_query(inp) == (inp, None, None)
|
||||
@@ -32,9 +32,9 @@ FakeResponse = namedtuple('FakeResponse', ['status', 'output', 'content_type'])
|
||||
|
||||
class FakeAdaptor(glue.ASGIAdaptor):
|
||||
|
||||
def __init__(self, params={}, headers={}, config=None):
|
||||
self.params = params
|
||||
self.headers = headers
|
||||
def __init__(self, params=None, headers=None, config=None):
|
||||
self.params = params or {}
|
||||
self.headers = headers or {}
|
||||
self._config = config or Configuration(None)
|
||||
|
||||
|
||||
@@ -386,6 +386,63 @@ class TestDetailsEndpoint:
|
||||
await glue.details_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
|
||||
# reverse_endpoint()
|
||||
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))
|
||||
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):
|
||||
a = FakeAdaptor()
|
||||
a.params = params
|
||||
a.params['format'] = 'xml'
|
||||
|
||||
with pytest.raises(FakeError, match='^400 -- (?s:.*)missing'):
|
||||
await glue.reverse_endpoint(napi.NominatimAPIAsync(Path('/invalid')), 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(Path('/invalid')), a)
|
||||
|
||||
assert res == ''
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reverse_success(self):
|
||||
a = FakeAdaptor()
|
||||
a.params['lat'] = '56.3'
|
||||
a.params['lon'] = '6.8'
|
||||
|
||||
assert await glue.reverse_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reverse_from_search(self):
|
||||
a = FakeAdaptor()
|
||||
a.params['q'] = '34.6 2.56'
|
||||
a.params['format'] = 'json'
|
||||
|
||||
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
assert len(json.loads(res.output)) == 1
|
||||
|
||||
|
||||
# lookup_endpoint()
|
||||
|
||||
class TestLookupEndpoint:
|
||||
@@ -444,3 +501,111 @@ class TestLookupEndpoint:
|
||||
res = await glue.lookup_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
assert len(json.loads(res.output)) == 1
|
||||
|
||||
|
||||
# search_endpoint()
|
||||
|
||||
class TestSearchEndPointSearch:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_lookup_func(self, monkeypatch):
|
||||
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()
|
||||
a.params['q'] = 'something'
|
||||
|
||||
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
assert len(json.loads(res.output)) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_free_text_xml(self):
|
||||
a = FakeAdaptor()
|
||||
a.params['q'] = 'something'
|
||||
a.params['format'] = 'xml'
|
||||
|
||||
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
assert res.status == 200
|
||||
assert res.output.index('something') > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_free_and_structured(self):
|
||||
a = FakeAdaptor()
|
||||
a.params['q'] = 'something'
|
||||
a.params['city'] = 'ignored'
|
||||
|
||||
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
assert len(json.loads(res.output)) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('dedupe,numres', [(True, 1), (False, 2)])
|
||||
async def test_search_dedupe(self, dedupe, numres):
|
||||
self.results = self.results * 2
|
||||
a = FakeAdaptor()
|
||||
a.params['q'] = 'something'
|
||||
if not dedupe:
|
||||
a.params['dedupe'] = '0'
|
||||
|
||||
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
assert len(json.loads(res.output)) == numres
|
||||
|
||||
|
||||
class TestSearchEndPointSearchAddress:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_lookup_func(self, monkeypatch):
|
||||
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()
|
||||
a.params['street'] = 'something'
|
||||
|
||||
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
assert len(json.loads(res.output)) == 1
|
||||
|
||||
|
||||
class TestSearchEndPointSearchCategory:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_lookup_func(self, monkeypatch):
|
||||
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()
|
||||
a.params['q'] = '[shop=fog]'
|
||||
|
||||
res = await glue.search_endpoint(napi.NominatimAPIAsync(Path('/invalid')), a)
|
||||
|
||||
assert len(json.loads(res.output)) == 1
|
||||
|
||||
Reference in New Issue
Block a user