add unit tests for new Python API

This commit is contained in:
Sarah Hoffmann
2022-12-07 19:47:46 +01:00
parent cf19036ce6
commit 9d31a67116
7 changed files with 230 additions and 6 deletions

View File

@@ -38,6 +38,14 @@ class NominatimAPIAsync:
future=True)
async def close(self) -> None:
""" Close all active connections to the database. The NominatimAPIAsync
object remains usable after closing. If a new API functions is
called, new connections are created.
"""
await self.engine.dispose()
async def status(self) -> StatusResult:
""" Return the status of the database.
"""
@@ -53,6 +61,14 @@ class NominatimAPI:
self.async_api = NominatimAPIAsync(project_dir, environ)
def close(self) -> None:
""" Close all active connections to the database. The NominatimAPIAsync
object remains usable after closing. If a new API functions is
called, new connections are created.
"""
asyncio.get_event_loop().run_until_complete(self.async_api.close())
def status(self) -> StatusResult:
""" Return the status of the database.
"""

View File

@@ -60,7 +60,7 @@ async def get_status(engine: AsyncEngine) -> StatusResult:
async with engine.begin() as conn:
status.data_updated = await _get_database_date(conn)
status.database_version = await _get_database_version(conn)
except asyncpg.PostgresError as err:
return StatusResult(700, str(err))
except asyncpg.PostgresError:
return StatusResult(700, 'No database')
return status

View File

@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2022 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Helper fixtures for API call tests.
"""
from pathlib import Path
import pytest
import time
from nominatim.api import NominatimAPI
@pytest.fixture
def apiobj(temp_db):
""" Create an asynchronous SQLAlchemy engine for the test DB.
"""
api = NominatimAPI(Path('/invalid'), {})
yield api
api.close()

View File

@@ -0,0 +1,60 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2022 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for the status API call.
"""
from pathlib import Path
import datetime as dt
import pytest
from nominatim.version import version_str
from nominatim.api import NominatimAPI
def test_status_no_extra_info(apiobj, table_factory):
table_factory('import_status',
definition="lastimportdate timestamp with time zone NOT NULL")
table_factory('nominatim_properties',
definition='property TEXT, value TEXT')
result = apiobj.status()
assert result.status == 0
assert result.message == 'OK'
assert result.software_version == version_str()
assert result.database_version is None
assert result.data_updated is None
def test_status_full(apiobj, table_factory):
table_factory('import_status',
definition="lastimportdate timestamp with time zone NOT NULL",
content=(('2022-12-07 15:14:46+01',),))
table_factory('nominatim_properties',
definition='property TEXT, value TEXT',
content=(('database_version', '99.5.4-2'), ))
result = apiobj.status()
assert result.status == 0
assert result.message == 'OK'
assert result.software_version == version_str()
assert result.database_version == '99.5.4-2'
assert result.data_updated == dt.datetime(2022, 12, 7, 14, 14, 46, 0, tzinfo=dt.timezone.utc)
def test_status_database_not_found(monkeypatch):
monkeypatch.setenv('NOMINATIM_DATABASE_DSN', 'dbname=rgjdfkgjedkrgdfkngdfkg')
api = NominatimAPI(Path('/invalid'), {})
result = api.status()
assert result.status == 700
assert result.message == 'No database'
assert result.software_version == version_str()
assert result.database_version is None
assert result.data_updated is None

View File

@@ -11,6 +11,7 @@ These tests just check that the various command line parameters route to the
correct functionionality. They use a lot of monkeypatching to avoid executing
the actual functions.
"""
import importlib
import pytest
import nominatim.indexer.indexer
@@ -59,7 +60,7 @@ def test_cli_add_data_tiger_data(cli_call, cli_tokenizer_mock, mock_func_factory
assert mock.called == 1
def test_cli_serve_command(cli_call, mock_func_factory):
def test_cli_serve_php(cli_call, mock_func_factory):
func = mock_func_factory(nominatim.cli, 'run_php_server')
cli_call('serve') == 0
@@ -67,6 +68,47 @@ def test_cli_serve_command(cli_call, mock_func_factory):
assert func.called == 1
def test_cli_serve_sanic(cli_call, mock_func_factory):
mod = pytest.importorskip("sanic")
func = mock_func_factory(mod.Sanic, "run")
cli_call('serve', '--engine', 'sanic') == 0
assert func.called == 1
def test_cli_serve_starlette_custom_server(cli_call, mock_func_factory):
pytest.importorskip("starlette")
mod = pytest.importorskip("uvicorn")
func = mock_func_factory(mod, "run")
cli_call('serve', '--engine', 'starlette', '--server', 'foobar:4545') == 0
assert func.called == 1
assert func.last_kwargs['host'] == 'foobar'
assert func.last_kwargs['port'] == 4545
def test_cli_serve_starlette_custom_server_bad_port(cli_call, mock_func_factory):
pytest.importorskip("starlette")
mod = pytest.importorskip("uvicorn")
func = mock_func_factory(mod, "run")
cli_call('serve', '--engine', 'starlette', '--server', 'foobar:45:45') == 1
@pytest.mark.parametrize("engine", ['falcon', 'starlette'])
def test_cli_serve_uvicorn_based(cli_call, engine, mock_func_factory):
pytest.importorskip(engine)
mod = pytest.importorskip("uvicorn")
func = mock_func_factory(mod, "run")
cli_call('serve', '--engine', engine) == 0
assert func.called == 1
assert func.last_kwargs['host'] == '127.0.0.1'
assert func.last_kwargs['port'] == 8088
def test_cli_export_command(cli_call, mock_run_legacy):
assert cli_call('export', '--output-all-postcodes') == 0

View File

@@ -7,9 +7,12 @@
"""
Tests for API access commands of command-line interface wrapper.
"""
import json
import pytest
import nominatim.clicmd.api
import nominatim.api
from nominatim.apicmd.status import StatusResult
@pytest.mark.parametrize("endpoint", (('search', 'reverse', 'lookup', 'details', 'status')))
@@ -27,9 +30,8 @@ def test_no_api_without_phpcgi(endpoint):
('details', '--node', '1'),
('details', '--way', '1'),
('details', '--relation', '1'),
('details', '--place_id', '10001'),
('status',)])
class TestCliApiCall:
('details', '--place_id', '10001')])
class TestCliApiCallPhp:
@pytest.fixture(autouse=True)
def setup_cli_call(self, params, cli_call, mock_func_factory, tmp_path):
@@ -55,6 +57,29 @@ class TestCliApiCall:
assert self.run_nominatim() == 1
class TestCliStatusCall:
@pytest.fixture(autouse=True)
def setup_status_mock(self, monkeypatch):
monkeypatch.setattr(nominatim.api.NominatimAPI, 'status',
lambda self: StatusResult(200, 'OK'))
def test_status_simple(self, cli_call, tmp_path):
result = cli_call('status', '--project-dir', str(tmp_path))
assert result == 0
def test_status_json_format(self, cli_call, tmp_path, capsys):
result = cli_call('status', '--project-dir', str(tmp_path),
'--format', 'json')
assert result == 0
json.loads(capsys.readouterr().out)
QUERY_PARAMS = {
'search': ('--query', 'somewhere'),
'reverse': ('--lat', '20', '--lon', '30'),

View File

@@ -0,0 +1,59 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2022 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Tests for formatting results for the V1 API.
"""
import datetime as dt
import pytest
import nominatim.result_formatter.v1 as format_module
from nominatim.apicmd.status import StatusResult
from nominatim.version import version_str
STATUS_FORMATS = {'text', 'json'}
class TestStatusResultFormat:
@pytest.fixture(autouse=True)
def make_formatter(self):
self.formatter = format_module.create(StatusResult)
def test_format_list(self):
assert set(self.formatter.list_formats()) == STATUS_FORMATS
@pytest.mark.parametrize('fmt', list(STATUS_FORMATS))
def test_supported(self, fmt):
assert self.formatter.supports_format(fmt)
def test_unsupported(self):
assert not self.formatter.supports_format('gagaga')
def test_format_text(self):
assert self.formatter.format(StatusResult(0, 'message here'), 'text') == 'message here'
def test_format_json_minimal(self):
status = StatusResult(700, 'Bad format.')
result = self.formatter.format(status, 'json')
assert result == '{"status": 700, "message": "Bad format.", "software_version": "%s"}' % (version_str())
def test_format_json_full(self):
status = StatusResult(0, 'OK')
status.data_updated = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
status.database_version = '5.6'
result = self.formatter.format(status, 'json')
assert result == '{"status": 0, "message": "OK", "data_updated": "2010-02-07T20:20:03+00:00", "software_version": "%s", "database_version": "5.6"}' % (version_str())