--- /dev/null
+@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 |
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'])]
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):
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:
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"