add tests for details result formatting and trim results

Values that are None are no longer included in the output to save
a bit of bandwidth.
This commit is contained in:
Sarah Hoffmann 2023-02-04 14:17:47 +01:00
parent b742200442
commit 42c3754dcd
5 changed files with 206 additions and 23 deletions

View File

@ -20,6 +20,7 @@ from .status import (StatusResult as StatusResult)
from .types import (PlaceID as PlaceID, from .types import (PlaceID as PlaceID,
OsmID as OsmID, OsmID as OsmID,
PlaceRef as PlaceRef, PlaceRef as PlaceRef,
Point as Point,
GeometryFormat as GeometryFormat, GeometryFormat as GeometryFormat,
LookupDetails as LookupDetails) LookupDetails as LookupDetails)
from .results import (SourceTable as SourceTable, from .results import (SourceTable as SourceTable,

View File

@ -105,6 +105,9 @@ class SearchResult:
geometry: Dict[str, str] = dataclasses.field(default_factory=dict) geometry: Dict[str, str] = dataclasses.field(default_factory=dict)
def __post_init__(self) -> None:
if self.indexed_date is not None and self.indexed_date.tzinfo is None:
self.indexed_date = self.indexed_date.replace(tzinfo=dt.timezone.utc)
@property @property
def lat(self) -> float: def lat(self) -> float:

View File

@ -45,7 +45,7 @@ def _add_address_row(writer: JsonWriter, row: napi.AddressLine,
locales: napi.Locales) -> None: locales: napi.Locales) -> None:
writer.start_object()\ writer.start_object()\
.keyval('localname', locales.display_name(row.names))\ .keyval('localname', locales.display_name(row.names))\
.keyval('place_id', row.place_id) .keyval_not_none('place_id', row.place_id)
if row.osm_object is not None: if row.osm_object is not None:
writer.keyval('osm_id', row.osm_object[1])\ writer.keyval('osm_id', row.osm_object[1])\
@ -100,8 +100,8 @@ def _format_search_json(result: napi.SearchResult, options: Mapping[str, Any]) -
out = JsonWriter() out = JsonWriter()
out.start_object()\ out.start_object()\
.keyval('place_id', result.place_id)\ .keyval_not_none('place_id', result.place_id)\
.keyval('parent_place_id', result.parent_place_id) .keyval_not_none('parent_place_id', result.parent_place_id)
if result.osm_object is not None: if result.osm_object is not None:
out.keyval('osm_type', result.osm_object[0])\ out.keyval('osm_type', result.osm_object[0])\
@ -111,16 +111,16 @@ def _format_search_json(result: napi.SearchResult, options: Mapping[str, Any]) -
.keyval('type', result.category[1])\ .keyval('type', result.category[1])\
.keyval('admin_level', result.admin_level)\ .keyval('admin_level', result.admin_level)\
.keyval('localname', locales.display_name(result.names))\ .keyval('localname', locales.display_name(result.names))\
.keyval('names', result.names or [])\ .keyval_not_none('names', result.names or None)\
.keyval('addresstags', result.address or [])\ .keyval_not_none('addresstags', result.address or None)\
.keyval('housenumber', result.housenumber)\ .keyval_not_none('housenumber', result.housenumber)\
.keyval('calculated_postcode', result.postcode)\ .keyval_not_none('calculated_postcode', result.postcode)\
.keyval('country_code', result.country_code)\ .keyval_not_none('country_code', result.country_code)\
.keyval_not_none('indexed_date', result.indexed_date, lambda v: v.isoformat())\ .keyval_not_none('indexed_date', result.indexed_date, lambda v: v.isoformat())\
.keyval('importance', result.importance)\ .keyval_not_none('importance', result.importance)\
.keyval('calculated_importance', result.calculated_importance())\ .keyval('calculated_importance', result.calculated_importance())\
.keyval('extratags', result.extratags or [])\ .keyval_not_none('extratags', result.extratags or None)\
.keyval('calculated_wikipedia', result.wikipedia)\ .keyval_not_none('calculated_wikipedia', result.wikipedia)\
.keyval('rank_address', result.rank_address)\ .keyval('rank_address', result.rank_address)\
.keyval('rank_search', result.rank_search)\ .keyval('rank_search', result.rank_search)\
.keyval('isarea', 'Polygon' in (geom or result.geometry.get('type') or ''))\ .keyval('isarea', 'Polygon' in (geom or result.geometry.get('type') or ''))\

View File

@ -58,7 +58,7 @@ def test_lookup_in_placex(apiobj, idobj):
assert result.importance == pytest.approx(0.01) assert result.importance == pytest.approx(0.01)
assert result.country_code == 'gb' assert result.country_code == 'gb'
assert result.indexed_date == import_date assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
assert result.address_rows is None assert result.address_rows is None
assert result.linked_rows is None assert result.linked_rows is None
@ -106,7 +106,7 @@ def test_lookup_in_placex_minimal_info(apiobj):
assert result.importance is None assert result.importance is None
assert result.country_code is None assert result.country_code is None
assert result.indexed_date == import_date assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
assert result.address_rows is None assert result.address_rows is None
assert result.linked_rows is None assert result.linked_rows is None
@ -290,7 +290,7 @@ def test_lookup_in_osmline(apiobj, idobj):
assert result.importance is None assert result.importance is None
assert result.country_code == 'gb' assert result.country_code == 'gb'
assert result.indexed_date == import_date assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
assert result.address_rows is None assert result.address_rows is None
assert result.linked_rows is None assert result.linked_rows is None
@ -506,7 +506,7 @@ def test_lookup_in_postcode(apiobj):
assert result.importance is None assert result.importance is None
assert result.country_code == 'gb' assert result.country_code == 'gb'
assert result.indexed_date == import_date assert result.indexed_date == import_date.replace(tzinfo=dt.timezone.utc)
assert result.address_rows is None assert result.address_rows is None
assert result.linked_rows is None assert result.linked_rows is None
@ -559,6 +559,15 @@ def test_lookup_postcode_with_address_details(apiobj):
rank_address=4, distance=0.0) rank_address=4, distance=0.0)
] ]
@pytest.mark.parametrize('objid', [napi.PlaceID(1736),
napi.OsmID('W', 55),
napi.OsmID('N', 55, 'amenity')])
def test_lookup_missing_object(apiobj, objid):
apiobj.add_placex(place_id=1, osm_type='N', osm_id=55,
class_='place', type='suburb')
assert apiobj.api.lookup(objid, napi.LookupDetails()) is None
@pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML, @pytest.mark.parametrize('gtype', (napi.GeometryFormat.KML,
napi.GeometryFormat.SVG, napi.GeometryFormat.SVG,

View File

@ -8,10 +8,12 @@
Tests for formatting results for the V1 API. Tests for formatting results for the V1 API.
""" """
import datetime as dt import datetime as dt
import json
import pytest import pytest
import nominatim.api.v1 as api_impl import nominatim.api.v1 as api_impl
from nominatim.api import StatusResult import nominatim.api as napi
from nominatim.version import NOMINATIM_VERSION from nominatim.version import NOMINATIM_VERSION
STATUS_FORMATS = {'text', 'json'} STATUS_FORMATS = {'text', 'json'}
@ -19,28 +21,28 @@ STATUS_FORMATS = {'text', 'json'}
# StatusResult # StatusResult
def test_status_format_list(): def test_status_format_list():
assert set(api_impl.list_formats(StatusResult)) == STATUS_FORMATS assert set(api_impl.list_formats(napi.StatusResult)) == STATUS_FORMATS
@pytest.mark.parametrize('fmt', list(STATUS_FORMATS)) @pytest.mark.parametrize('fmt', list(STATUS_FORMATS))
def test_status_supported(fmt): def test_status_supported(fmt):
assert api_impl.supports_format(StatusResult, fmt) assert api_impl.supports_format(napi.StatusResult, fmt)
def test_status_unsupported(): def test_status_unsupported():
assert not api_impl.supports_format(StatusResult, 'gagaga') assert not api_impl.supports_format(napi.StatusResult, 'gagaga')
def test_status_format_text(): def test_status_format_text():
assert api_impl.format_result(StatusResult(0, 'message here'), 'text', {}) == 'OK' assert api_impl.format_result(napi.StatusResult(0, 'message here'), 'text', {}) == 'OK'
def test_status_format_text(): def test_status_format_text():
assert api_impl.format_result(StatusResult(500, 'message here'), 'text', {}) == 'ERROR: message here' assert api_impl.format_result(napi.StatusResult(500, 'message here'), 'text', {}) == 'ERROR: message here'
def test_status_format_json_minimal(): def test_status_format_json_minimal():
status = StatusResult(700, 'Bad format.') status = napi.StatusResult(700, 'Bad format.')
result = api_impl.format_result(status, 'json', {}) result = api_impl.format_result(status, 'json', {})
@ -48,10 +50,178 @@ def test_status_format_json_minimal():
def test_status_format_json_full(): def test_status_format_json_full():
status = StatusResult(0, 'OK') status = napi.StatusResult(0, 'OK')
status.data_updated = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc) status.data_updated = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
status.database_version = '5.6' status.database_version = '5.6'
result = api_impl.format_result(status, 'json', {}) result = api_impl.format_result(status, 'json', {})
assert result == '{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"%s","database_version":"5.6"}' % (NOMINATIM_VERSION, ) assert result == '{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"%s","database_version":"5.6"}' % (NOMINATIM_VERSION, )
# SearchResult
def test_search_details_minimal():
search = napi.SearchResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0))
result = api_impl.format_result(search, 'details-json', {})
assert json.loads(result) == \
{'category': 'place',
'type': 'thing',
'admin_level': 15,
'localname': '',
'calculated_importance': pytest.approx(0.0000001),
'rank_address': 30,
'rank_search': 30,
'isarea': False,
'centroid': {'type': 'Point', 'coordinates': [1.0, 2.0]},
'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]},
}
def test_search_details_full():
import_date = dt.datetime(2010, 2, 7, 20, 20, 3, 0)
search = napi.SearchResult(
source_table=napi.SourceTable.PLACEX,
category=('amenity', 'bank'),
centroid=napi.Point(56.947, -87.44),
place_id=37563,
parent_place_id=114,
linked_place_id=55693,
osm_object=('W', 442100),
admin_level=14,
names={'name': 'Bank', 'name:fr': 'Banque'},
address={'city': 'Niento', 'housenumber': ' 3'},
extratags={'atm': 'yes'},
housenumber='3',
postcode='556 X23',
wikipedia='en:Bank',
rank_address=29,
rank_search=28,
importance=0.0443,
country_code='ll',
indexed_date = import_date
)
result = api_impl.format_result(search, 'details-json', {})
assert json.loads(result) == \
{'place_id': 37563,
'parent_place_id': 114,
'osm_type': 'W',
'osm_id': 442100,
'category': 'amenity',
'type': 'bank',
'admin_level': 14,
'localname': 'Bank',
'names': {'name': 'Bank', 'name:fr': 'Banque'},
'addresstags': {'city': 'Niento', 'housenumber': ' 3'},
'housenumber': '3',
'calculated_postcode': '556 X23',
'country_code': 'll',
'indexed_date': '2010-02-07T20:20:03+00:00',
'importance': pytest.approx(0.0443),
'calculated_importance': pytest.approx(0.0443),
'extratags': {'atm': 'yes'},
'calculated_wikipedia': 'en:Bank',
'rank_address': 29,
'rank_search': 28,
'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),
('ST_LineString', False),
('ST_Polygon', True),
('ST_MultiPolygon', True)])
def test_search_details_no_geometry(gtype, isarea):
search = napi.SearchResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0),
geometry={'type': gtype})
result = api_impl.format_result(search, 'details-json', {})
js = json.loads(result)
assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
assert js['isarea'] == isarea
def test_search_details_with_geometry():
search = napi.SearchResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0),
geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
result = api_impl.format_result(search, 'details-json', {})
js = json.loads(result)
assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
assert js['isarea'] == False
def test_search_details_with_address_minimal():
search = napi.SearchResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0),
address_rows=[
napi.AddressLine(place_id=None,
osm_object=None,
category=('bnd', 'note'),
names={},
extratags=None,
admin_level=None,
fromarea=False,
isaddress=False,
rank_address=10,
distance=0.0)
])
result = api_impl.format_result(search, 'details-json', {})
js = json.loads(result)
assert js['address'] == [{'localname': '',
'class': 'bnd',
'type': 'note',
'rank_address': 10,
'distance': 0.0,
'isaddress': False}]
def test_search_details_with_address_full():
search = napi.SearchResult(napi.SourceTable.PLACEX,
('place', 'thing'),
napi.Point(1.0, 2.0),
address_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 = api_impl.format_result(search, 'details-json', {})
js = json.loads(result)
assert js['address'] == [{'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}]