extend BDD API tests to query via Python frameworks

A new config option ENGINE allows to choose between php and any of the
supported Python engines.
This commit is contained in:
Sarah Hoffmann 2022-12-06 11:20:50 +01:00
parent d7bc846c3c
commit 7219ee6532
6 changed files with 83 additions and 11 deletions

View File

@ -7,7 +7,7 @@
"""
Server implementation using the falcon webserver framework.
"""
from typing import Type, Any
from typing import Type, Any, Optional, Mapping
from pathlib import Path
import falcon
@ -26,8 +26,8 @@ class NominatimV1:
""" Implementation of V1 version of the Nominatim API.
"""
def __init__(self, project_dir: Path) -> None:
self.api = NominatimAPIAsync(project_dir)
def __init__(self, project_dir: Path, environ: Optional[Mapping[str, str]]) -> None:
self.api = NominatimAPIAsync(project_dir, environ)
self.formatters = {}
for rtype in (StatusResult, ):
@ -67,12 +67,13 @@ class NominatimV1:
self.format_response(req, resp, result)
def get_application(project_dir: Path) -> falcon.asgi.App:
def get_application(project_dir: Path,
environ: Optional[Mapping[str, str]] = None) -> falcon.asgi.App:
""" Create a Nominatim falcon ASGI application.
"""
app = falcon.asgi.App()
api = NominatimV1(project_dir)
api = NominatimV1(project_dir, environ)
app.add_route('/status', api, suffix='status')

View File

@ -7,7 +7,7 @@
"""
Server implementation using the sanic webserver framework.
"""
from typing import Any, Optional
from typing import Any, Optional, Mapping
from pathlib import Path
import sanic
@ -64,12 +64,13 @@ async def status(request: sanic.Request) -> sanic.HTTPResponse:
return api_response(request,await request.app.ctx.api.status())
def get_application(project_dir: Path) -> sanic.Sanic:
def get_application(project_dir: Path,
environ: Optional[Mapping[str, str]] = None) -> sanic.Sanic:
""" Create a Nominatim sanic ASGI application.
"""
app = sanic.Sanic("NominatimInstance")
app.ctx.api = NominatimAPIAsync(project_dir)
app.ctx.api = NominatimAPIAsync(project_dir, environ)
app.ctx.formatters = {}
for rtype in (StatusResult, ):
app.ctx.formatters[rtype] = formatting.create(rtype)

View File

@ -7,7 +7,7 @@
"""
Server implementation using the starlette webserver framework.
"""
from typing import Any, Type
from typing import Any, Type, Optional, Mapping
from pathlib import Path
from starlette.applications import Starlette
@ -67,11 +67,12 @@ V1_ROUTES = [
Route('/status', endpoint=on_status)
]
def get_application(project_dir: Path) -> Starlette:
def get_application(project_dir: Path,
environ: Optional[Mapping[str, str]] = None) -> Starlette:
""" Create a Nominatim falcon ASGI application.
"""
app = Starlette(debug=True, routes=V1_ROUTES)
app.state.API = NominatimAPIAsync(project_dir)
app.state.API = NominatimAPIAsync(project_dir, environ)
return app

View File

@ -28,6 +28,7 @@ userconfig = {
'SERVER_MODULE_PATH' : None,
'TOKENIZER' : None, # Test with a custom tokenizer
'STYLE' : 'extratags',
'API_ENGINE': 'php',
'PHPCOV' : False, # set to output directory to enable code coverage
}

View File

@ -5,9 +5,13 @@
# Copyright (C) 2022 by the Nominatim developer community.
# For a full list of authors see the git log.
from pathlib import Path
import importlib
import sys
import tempfile
from asgi_lifespan import LifespanManager
import httpx
import psycopg2
import psycopg2.extras
@ -49,6 +53,12 @@ class NominatimEnvironment:
self.api_db_done = False
self.website_dir = None
self.api_engine = None
if config['API_ENGINE'] != 'php':
if not hasattr(self, f"create_api_request_func_{config['API_ENGINE']}"):
raise RuntimeError(f"Unknown API engine '{config['API_ENGINE']}'")
self.api_engine = getattr(self, f"create_api_request_func_{config['API_ENGINE']}")()
def connect_database(self, dbname):
""" Return a connection to the database with the given name.
Uses configured host, user and port.
@ -323,3 +333,49 @@ class NominatimEnvironment:
WHERE class='place' and type='houses'
and osm_type='W'
and ST_GeometryType(geometry) = 'ST_LineString'""")
def create_api_request_func_starlette(self):
import nominatim.server.starlette.server
async def _request(endpoint, params, project_dir, environ):
app = nominatim.server.starlette.server.get_application(project_dir, environ)
async with LifespanManager(app):
async with httpx.AsyncClient(app=app, base_url="http://nominatim.test") as client:
response = await client.get(f"/{endpoint}", params=params)
return response.text, response.status_code
return _request
def create_api_request_func_sanic(self):
import nominatim.server.sanic.server
async def _request(endpoint, params, project_dir, environ):
app = nominatim.server.sanic.server.get_application(project_dir, environ)
_, response = await app.asgi_client.get(f"/{endpoint}", params=params)
return response.text, response.status_code
return _request
def create_api_request_func_falcon(self):
import nominatim.server.falcon.server
import falcon.testing
async def _request(endpoint, params, project_dir, environ):
app = nominatim.server.falcon.server.get_application(project_dir, environ)
async with falcon.testing.ASGIConductor(app) as conductor:
response = await conductor.get(f"/{endpoint}", params=params)
return response.text, response.status_code
return _request

View File

@ -9,10 +9,12 @@
Queries may either be run directly via PHP using the query script
or via the HTTP interface using php-cgi.
"""
from pathlib import Path
import json
import os
import re
import logging
import asyncio
from urllib.parse import urlencode
from utils import run_script
@ -72,6 +74,16 @@ def send_api_query(endpoint, params, fmt, context):
for h in context.table.headings:
params[h] = context.table[0][h]
if context.nominatim.api_engine is None:
return send_api_query_php(endpoint, params, context)
return asyncio.run(context.nominatim.api_engine(endpoint, params,
Path(context.nominatim.website_dir.name),
context.nominatim.test_env))
def send_api_query_php(endpoint, params, context):
env = dict(BASE_SERVER_ENV)
env['QUERY_STRING'] = urlencode(params)