mirror of
https://github.com/osm-search/Nominatim.git
synced 2024-10-05 15:08:32 +03:00
more API search tests
also move directory name back to api
This commit is contained in:
parent
b9a58b8f24
commit
81922fc057
296
test/bdd/api/search/params.feature
Normal file
296
test/bdd/api/search/params.feature
Normal file
@ -0,0 +1,296 @@
|
||||
@APIDB
|
||||
Feature: Search queries
|
||||
Testing different queries and parameters
|
||||
|
||||
Scenario: Simple XML search
|
||||
When sending xml search query "Schaan"
|
||||
Then result 0 has attributes place_id,osm_type,osm_id
|
||||
And result 0 has attributes place_rank,boundingbox
|
||||
And result 0 has attributes lat,lon,display_name
|
||||
And result 0 has attributes class,type,importance,icon
|
||||
And result 0 has not attributes address
|
||||
And result 0 has bounding box in 46.5,47.5,9,10
|
||||
|
||||
Scenario: Simple JSON search
|
||||
When sending json search query "Vaduz"
|
||||
Then result 0 has attributes place_id,licence,icon,class,type
|
||||
And result 0 has attributes osm_type,osm_id,boundingbox
|
||||
And result 0 has attributes lat,lon,display_name,importance
|
||||
And result 0 has not attributes address
|
||||
And result 0 has bounding box in 46.5,47.5,9,10
|
||||
|
||||
Scenario: JSON search with addressdetails
|
||||
When sending json search query "Montevideo" with address
|
||||
Then address of result 0 is
|
||||
| type | value |
|
||||
| city | Montevideo |
|
||||
| state | Montevideo |
|
||||
| country | Uruguay |
|
||||
| country_code | uy |
|
||||
|
||||
Scenario: XML search with addressdetails
|
||||
When sending xml search query "Aleg" with address
|
||||
Then address of result 0 is
|
||||
| type | value |
|
||||
| city | Aleg |
|
||||
| state | Brakna |
|
||||
| country | Mauritania |
|
||||
| country_code | mr |
|
||||
|
||||
Scenario: coordinate search with addressdetails
|
||||
When sending json search query "14.271104294939,107.69828796387"
|
||||
Then results contain
|
||||
| display_name |
|
||||
| Plei Ya Rê, Kon Tum province, Vietnam |
|
||||
|
||||
Scenario: Address details with unknown class types
|
||||
When sending json search query "Hundeauslauf, Hamburg" with address
|
||||
Then results contain
|
||||
| ID | class | type |
|
||||
| 0 | leisure | dog_park |
|
||||
And result addresses contain
|
||||
| ID | address29 |
|
||||
| 0 | Hundeauslauf |
|
||||
And address of result 0 has no types leisure,dog_park
|
||||
|
||||
Scenario: Disabling deduplication
|
||||
When sending json search query "Sievekingsallee, Hamburg"
|
||||
Then there are no duplicates
|
||||
When sending json search query "Sievekingsallee, Hamburg"
|
||||
| dedupe |
|
||||
| 0 |
|
||||
Then there are duplicates
|
||||
|
||||
Scenario: Search with bounded viewbox in right area
|
||||
When sending json search query "restaurant" with address
|
||||
| bounded | viewbox |
|
||||
| 1 | 9.93027,53.61634,10.10073,53.54500 |
|
||||
Then result addresses contain
|
||||
| state |
|
||||
| Hamburg |
|
||||
|
||||
Scenario: Search with bounded viewboxlbrt in right area
|
||||
When sending json search query "restaurant" with address
|
||||
| bounded | viewboxlbrt |
|
||||
| 1 | 9.93027,53.54500,10.10073,53.61634 |
|
||||
Then result addresses contain
|
||||
| state |
|
||||
| Hamburg |
|
||||
|
||||
Scenario: No POI search with unbounded viewbox
|
||||
When sending json search query "restaurant"
|
||||
| viewbox |
|
||||
| 9.93027,53.61634,10.10073,53.54500 |
|
||||
Then results contain
|
||||
| display_name |
|
||||
| ^[^,]*[Rr]estaurant.* |
|
||||
|
||||
Scenario: bounded search remains within viewbox, even with no results
|
||||
When sending json search query "restaurant"
|
||||
| bounded | viewbox |
|
||||
| 1 | 43.5403125,-5.6563282,43.54285,-5.662003 |
|
||||
Then less than 1 result is returned
|
||||
|
||||
Scenario: bounded search remains within viewbox with results
|
||||
When sending json search query "restaurant"
|
||||
| bounded | viewbox |
|
||||
| 1 | 9.93027,53.61634,10.10073,53.54500 |
|
||||
Then result has bounding box in 53.54500,53.61634,9.93027,10.10073
|
||||
|
||||
Scenario: Prefer results within viewbox
|
||||
When sending json search query "25 de Mayo" with address
|
||||
| accept-language |
|
||||
| en |
|
||||
Then result addresses contain
|
||||
| ID | state |
|
||||
| 0 | Salto |
|
||||
When sending json search query "25 de Mayo" with address
|
||||
| accept-language | viewbox |
|
||||
| en | -56.35879,-34.18330,-56.31618,-34.20815 |
|
||||
Then result addresses contain
|
||||
| ID | state |
|
||||
| 0 | Florida |
|
||||
|
||||
Scenario: Overly large limit number for search results
|
||||
When sending json search query "restaurant"
|
||||
| limit |
|
||||
| 1000 |
|
||||
Then at most 50 results are returned
|
||||
|
||||
Scenario: Limit number of search results
|
||||
When sending json search query "restaurant"
|
||||
| limit |
|
||||
| 4 |
|
||||
Then exactly 4 results are returned
|
||||
|
||||
Scenario: Restrict to feature type country
|
||||
When sending xml search query "Uruguay"
|
||||
Then results contain
|
||||
| ID | place_rank |
|
||||
| 1 | 16 |
|
||||
When sending xml search query "Uruguay"
|
||||
| featureType |
|
||||
| country |
|
||||
Then results contain
|
||||
| place_rank |
|
||||
| 4 |
|
||||
|
||||
Scenario: Restrict to feature type state
|
||||
When sending xml search query "Dakota"
|
||||
Then results contain
|
||||
| place_rank |
|
||||
| 12 |
|
||||
When sending xml search query "Dakota"
|
||||
| featureType |
|
||||
| state |
|
||||
Then results contain
|
||||
| place_rank |
|
||||
| 8 |
|
||||
|
||||
Scenario: Restrict to feature type city
|
||||
When sending xml search query "vaduz"
|
||||
Then results contain
|
||||
| ID | place_rank |
|
||||
| 1 | 30 |
|
||||
When sending xml search query "vaduz"
|
||||
| featureType |
|
||||
| city |
|
||||
Then results contain
|
||||
| place_rank |
|
||||
| 16 |
|
||||
|
||||
Scenario: Restrict to feature type settlement
|
||||
When sending json search query "Burg"
|
||||
Then results contain
|
||||
| ID | class |
|
||||
| 1 | amenity |
|
||||
When sending json search query "Burg"
|
||||
| featureType |
|
||||
| settlement |
|
||||
Then results contain
|
||||
| class | type |
|
||||
| boundary | administrative |
|
||||
|
||||
Scenario Outline: Search with polygon threshold (json)
|
||||
When sending json search query "switzerland"
|
||||
| polygon_geojson | polygon_threshold |
|
||||
| 1 | <th> |
|
||||
Then at least 1 result is returned
|
||||
And result 0 has attributes geojson
|
||||
|
||||
Examples:
|
||||
| th |
|
||||
| -1 |
|
||||
| 0.0 |
|
||||
| 0.5 |
|
||||
| 999 |
|
||||
|
||||
Scenario Outline: Search with polygon threshold (xml)
|
||||
When sending xml search query "switzerland"
|
||||
| polygon_geojson | polygon_threshold |
|
||||
| 1 | <th> |
|
||||
Then at least 1 result is returned
|
||||
And result 0 has attributes geojson
|
||||
|
||||
Examples:
|
||||
| th |
|
||||
| -1 |
|
||||
| 0.0 |
|
||||
| 0.5 |
|
||||
| 999 |
|
||||
|
||||
Scenario Outline: Search with invalid polygon threshold (xml)
|
||||
When sending xml search query "switzerland"
|
||||
| polygon_geojson | polygon_threshold |
|
||||
| 1 | <th> |
|
||||
Then a HTTP 400 is returned
|
||||
|
||||
Examples:
|
||||
| th |
|
||||
| x |
|
||||
| ;; |
|
||||
| 1m |
|
||||
|
||||
Scenario Outline: Search with extratags
|
||||
When sending <format> search query "Hauptstr"
|
||||
| extratags |
|
||||
| 1 |
|
||||
Then result has attributes extratags
|
||||
|
||||
Examples:
|
||||
| format |
|
||||
| xml |
|
||||
| json |
|
||||
| jsonv2 |
|
||||
|
||||
Scenario Outline: Search with namedetails
|
||||
When sending <format> search query "Hauptstr"
|
||||
| namedetails |
|
||||
| 1 |
|
||||
Then result has attributes namedetails
|
||||
|
||||
Examples:
|
||||
| format |
|
||||
| xml |
|
||||
| json |
|
||||
| jsonv2 |
|
||||
|
||||
Scenario Outline: Search result with contains TEXT geometry
|
||||
When sending <format> search query "Highmore"
|
||||
| polygon_text |
|
||||
| 1 |
|
||||
Then result has attributes <response_attribute>
|
||||
|
||||
Examples:
|
||||
| format | response_attribute |
|
||||
| xml | geotext |
|
||||
| json | geotext |
|
||||
| jsonv2 | geotext |
|
||||
|
||||
Scenario Outline: Search result contains polygon-as-points geometry
|
||||
When sending <format> search query "Highmore"
|
||||
| polygon |
|
||||
| 1 |
|
||||
Then result has attributes <response_attribute>
|
||||
|
||||
Examples:
|
||||
| format | response_attribute |
|
||||
| xml | polygonpoints |
|
||||
| json | polygonpoints |
|
||||
| jsonv2 | polygonpoints |
|
||||
|
||||
Scenario Outline: Search result contains SVG geometry
|
||||
When sending <format> search query "Highmore"
|
||||
| polygon_svg |
|
||||
| 1 |
|
||||
Then result has attributes <response_attribute>
|
||||
|
||||
Examples:
|
||||
| format | response_attribute |
|
||||
| xml | geosvg |
|
||||
| json | svg |
|
||||
| jsonv2 | svg |
|
||||
|
||||
Scenario Outline: Search result contains KML geometry
|
||||
When sending <format> search query "Highmore"
|
||||
| polygon_kml |
|
||||
| 1 |
|
||||
Then result has attributes <response_attribute>
|
||||
|
||||
Examples:
|
||||
| format | response_attribute |
|
||||
| xml | geokml |
|
||||
| json | geokml |
|
||||
| jsonv2 | geokml |
|
||||
|
||||
Scenario Outline: Search result contains GEOJSON geometry
|
||||
When sending <format> search query "Highmore"
|
||||
| polygon_geojson |
|
||||
| 1 |
|
||||
Then result has attributes <response_attribute>
|
||||
|
||||
Examples:
|
||||
| format | response_attribute |
|
||||
| xml | geojson |
|
||||
| json | geojson |
|
||||
| jsonv2 | geojson |
|
58
test/bdd/api/search/queries.feature
Normal file
58
test/bdd/api/search/queries.feature
Normal file
@ -0,0 +1,58 @@
|
||||
@APIDB
|
||||
Feature: Search queries
|
||||
Generic search result correctness
|
||||
|
||||
Scenario: House number search for non-street address
|
||||
When sending json search query "2 Steinwald, Austria" with address
|
||||
| accept-language |
|
||||
| en |
|
||||
Then address of result 0 is
|
||||
| type | value |
|
||||
| house_number | 2 |
|
||||
| hamlet | Steinwald |
|
||||
| postcode | 6811 |
|
||||
| country | Austria |
|
||||
| country_code | at |
|
||||
|
||||
Scenario: House number interpolation even
|
||||
When sending json search query "Schellingstr 86, Hamburg" with address
|
||||
| accept-language |
|
||||
| de |
|
||||
Then address of result 0 is
|
||||
| type | value |
|
||||
| house_number | 86 |
|
||||
| road | Schellingstraße |
|
||||
| suburb | Eilbek |
|
||||
| postcode | 22089 |
|
||||
| city_district | Wandsbek |
|
||||
| state | Hamburg |
|
||||
| country | Deutschland |
|
||||
| country_code | de |
|
||||
|
||||
Scenario: House number interpolation odd
|
||||
When sending json search query "Schellingstr 73, Hamburg" with address
|
||||
| accept-language |
|
||||
| de |
|
||||
Then address of result 0 is
|
||||
| type | value |
|
||||
| house_number | 73 |
|
||||
| road | Schellingstraße |
|
||||
| suburb | Eilbek |
|
||||
| postcode | 22089 |
|
||||
| city_district | Wandsbek |
|
||||
| state | Hamburg |
|
||||
| country | Deutschland |
|
||||
| country_code | de |
|
||||
|
||||
@Tiger
|
||||
Scenario: TIGER house number
|
||||
When sending json search query "323 22nd Street Southwest, Huron"
|
||||
Then results contain
|
||||
| osm_type |
|
||||
| way |
|
||||
|
||||
Scenario: Search with class-type feature
|
||||
When sending jsonv2 search query "Hotel California"
|
||||
Then results contain
|
||||
| place_rank |
|
||||
| 30 |
|
@ -94,11 +94,29 @@ class SearchResponse(object):
|
||||
|
||||
self.header = dict(et.attrib)
|
||||
|
||||
|
||||
for child in et:
|
||||
assert_equal(child.tag, "place")
|
||||
self.result.append(dict(child.attrib))
|
||||
|
||||
address = {}
|
||||
for sub in child:
|
||||
if sub.tag == 'extratags':
|
||||
self.result[-1]['extratags'] = {}
|
||||
for tag in sub:
|
||||
self.result[-1]['extratags'][tag.attrib['key']] = tag.attrib['value']
|
||||
elif sub.tag == 'namedetails':
|
||||
self.result[-1]['namedetails'] = {}
|
||||
for tag in sub:
|
||||
self.result[-1]['namedetails'][tag.attrib['desc']] = tag.text
|
||||
elif sub.tag in ('geokml'):
|
||||
self.result[-1][sub.tag] = True
|
||||
else:
|
||||
address[sub.tag] = sub.text
|
||||
|
||||
if len(address) > 0:
|
||||
self.result[-1]['address'] = address
|
||||
|
||||
|
||||
def match_row(self, row):
|
||||
if 'ID' in row.headings:
|
||||
todo = [int(row['ID'])]
|
||||
@ -117,10 +135,18 @@ class SearchResponse(object):
|
||||
x, y = row[h].split(' ')
|
||||
assert_almost_equal(float(y), float(res['lat']))
|
||||
assert_almost_equal(float(x), float(res['lon']))
|
||||
elif row[h].startswith("^"):
|
||||
assert_in(h, res)
|
||||
assert_is_not_none(re.fullmatch(row[h], res[h]),
|
||||
"attribute '%s': expected: '%s', got '%s'"
|
||||
% (h, row[h], res[h]))
|
||||
else:
|
||||
assert_in(h, res)
|
||||
assert_equal(str(res[h]), str(row[h]))
|
||||
|
||||
def property_list(self, prop):
|
||||
return [ x[prop] for x in self.result ]
|
||||
|
||||
|
||||
@when(u'searching for "(?P<query>.*)"(?P<dups> with dups)?')
|
||||
def query_cmd(context, query, dups):
|
||||
@ -147,13 +173,15 @@ def query_cmd(context, query, dups):
|
||||
context.response = SearchResponse(outp.decode('utf-8'), 'json')
|
||||
|
||||
|
||||
@when(u'sending (?P<fmt>\S+ )?search query "(?P<query>.*)"')
|
||||
def website_search_request(context, fmt, query):
|
||||
@when(u'sending (?P<fmt>\S+ )?search query "(?P<query>.*)"(?P<addr> with address)?')
|
||||
def website_search_request(context, fmt, query, addr):
|
||||
env = BASE_SERVER_ENV
|
||||
|
||||
params = { 'q' : query }
|
||||
if fmt is not None:
|
||||
params['format'] = fmt.strip()
|
||||
if addr is not None:
|
||||
params['addressdetails'] = '1'
|
||||
if context.table:
|
||||
if context.table.headings[0] == 'param':
|
||||
for line in context.table:
|
||||
@ -241,13 +269,101 @@ def step_impl(context):
|
||||
for line in context.table:
|
||||
context.response.match_row(line)
|
||||
|
||||
@then(u'result (?P<lid>\d+) has (?P<neg>not )?attributes (?P<attrs>.*)')
|
||||
@then(u'result (?P<lid>\d+ )?has (?P<neg>not )?attributes (?P<attrs>.*)')
|
||||
def validate_attributes(context, lid, neg, attrs):
|
||||
context.execute_steps("then at least %s result is returned" % lid)
|
||||
if lid is None:
|
||||
idx = range(len(context.response.result))
|
||||
context.execute_steps("then at least 1 result is returned")
|
||||
else:
|
||||
idx = [int(lid.strip())]
|
||||
context.execute_steps("then more than %sresults are returned" % lid)
|
||||
|
||||
for i in idx:
|
||||
for attr in attrs.split(','):
|
||||
if neg:
|
||||
assert_not_in(attr, context.response.result[i])
|
||||
else:
|
||||
assert_in(attr, context.response.result[i])
|
||||
|
||||
@then(u'result addresses contain')
|
||||
def step_impl(context):
|
||||
context.execute_steps("then at least 1 result is returned")
|
||||
|
||||
if 'ID' not in context.table.headings:
|
||||
addr_parts = context.response.property_list('address')
|
||||
|
||||
for line in context.table:
|
||||
if 'ID' in context.table.headings:
|
||||
addr_parts = [dict(context.response.result[int(line['ID'])]['address'])]
|
||||
|
||||
for h in context.table.headings:
|
||||
if h != 'ID':
|
||||
for p in addr_parts:
|
||||
assert_in(h, p)
|
||||
assert_equal(p[h], line[h], "Bad address value for %s" % h)
|
||||
|
||||
@then(u'address of result (?P<lid>\d+) has(?P<neg> no)? types (?P<attrs>.*)')
|
||||
def check_address(context, lid, neg, attrs):
|
||||
context.execute_steps("then more than %s results are returned" % lid)
|
||||
|
||||
addr_parts = context.response.result[int(lid)]['address']
|
||||
|
||||
for attr in attrs.split(','):
|
||||
if neg:
|
||||
assert_not_in(attr, context.response.result[int(lid)])
|
||||
assert_not_in(attr, addr_parts)
|
||||
else:
|
||||
assert_in(attr, context.response.result[int(lid)])
|
||||
assert_in(attr, addr_parts)
|
||||
|
||||
@then(u'address of result (?P<lid>\d+) is')
|
||||
def check_address(context, lid):
|
||||
context.execute_steps("then more than %s results are returned" % lid)
|
||||
|
||||
addr_parts = dict(context.response.result[int(lid)]['address'])
|
||||
|
||||
for line in context.table:
|
||||
assert_in(line['type'], addr_parts)
|
||||
assert_equal(addr_parts[line['type']], line['value'],
|
||||
"Bad address value for %s" % line['type'])
|
||||
del addr_parts[line['type']]
|
||||
|
||||
eq_(0, len(addr_parts), "Additional address parts found: %s" % str(addr_parts))
|
||||
|
||||
@then(u'result (?P<lid>\d+ )?has bounding box in (?P<coords>[\d,.-]+)')
|
||||
def step_impl(context, lid, coords):
|
||||
if lid is None:
|
||||
context.execute_steps("then at least 1 result is returned")
|
||||
bboxes = context.response.property_list('boundingbox')
|
||||
else:
|
||||
context.execute_steps("then more than %sresults are returned" % lid)
|
||||
bboxes = [ context.response.result[int(lid)]['boundingbox']]
|
||||
|
||||
coord = [ float(x) for x in coords.split(',') ]
|
||||
|
||||
for bbox in bboxes:
|
||||
if isinstance(bbox, str):
|
||||
bbox = bbox.split(',')
|
||||
bbox = [ float(x) for x in bbox ]
|
||||
|
||||
assert_greater_equal(bbox[0], coord[0])
|
||||
assert_less_equal(bbox[1], coord[1])
|
||||
assert_greater_equal(bbox[2], coord[2])
|
||||
assert_less_equal(bbox[3], coord[3])
|
||||
|
||||
@then(u'there are(?P<neg> no)? duplicates')
|
||||
def check_for_duplicates(context, neg):
|
||||
context.execute_steps("then at least 1 result is returned")
|
||||
|
||||
resarr = set()
|
||||
has_dupe = False
|
||||
|
||||
for res in context.response.result:
|
||||
dup = (res['osm_type'], res['class'], res['type'], res['display_name'])
|
||||
if dup in resarr:
|
||||
has_dupe = True
|
||||
break
|
||||
resarr.add(dup)
|
||||
|
||||
if neg:
|
||||
assert not has_dupe, "Found duplicate for %s" % (dup, )
|
||||
else:
|
||||
assert has_dupe, "No duplicates found"
|
||||
|
Loading…
Reference in New Issue
Block a user