From: Sarah Hoffmann Date: Mon, 19 Dec 2016 20:38:42 +0000 (+0100) Subject: more API search tests X-Git-Tag: v3.0.0~85^2~9 X-Git-Url: https://git.openstreetmap.org./nominatim.git/commitdiff_plain/81922fc057b5a89fb41d05d3a31dab629ff0558a more API search tests also move directory name back to api --- diff --git a/test/bdd/api/search/params.feature b/test/bdd/api/search/params.feature new file mode 100644 index 00000000..65907379 --- /dev/null +++ b/test/bdd/api/search/params.feature @@ -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 | | + 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 | | + 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 | | + Then a HTTP 400 is returned + + Examples: + | th | + | x | + | ;; | + | 1m | + + Scenario Outline: Search with extratags + When sending search query "Hauptstr" + | extratags | + | 1 | + Then result has attributes extratags + + Examples: + | format | + | xml | + | json | + | jsonv2 | + + Scenario Outline: Search with namedetails + When sending 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 search query "Highmore" + | polygon_text | + | 1 | + Then result has attributes + + Examples: + | format | response_attribute | + | xml | geotext | + | json | geotext | + | jsonv2 | geotext | + + Scenario Outline: Search result contains polygon-as-points geometry + When sending search query "Highmore" + | polygon | + | 1 | + Then result has attributes + + Examples: + | format | response_attribute | + | xml | polygonpoints | + | json | polygonpoints | + | jsonv2 | polygonpoints | + + Scenario Outline: Search result contains SVG geometry + When sending search query "Highmore" + | polygon_svg | + | 1 | + Then result has attributes + + Examples: + | format | response_attribute | + | xml | geosvg | + | json | svg | + | jsonv2 | svg | + + Scenario Outline: Search result contains KML geometry + When sending search query "Highmore" + | polygon_kml | + | 1 | + Then result has attributes + + Examples: + | format | response_attribute | + | xml | geokml | + | json | geokml | + | jsonv2 | geokml | + + Scenario Outline: Search result contains GEOJSON geometry + When sending search query "Highmore" + | polygon_geojson | + | 1 | + Then result has attributes + + Examples: + | format | response_attribute | + | xml | geojson | + | json | geojson | + | jsonv2 | geojson | diff --git a/test/bdd/api/search/queries.feature b/test/bdd/api/search/queries.feature new file mode 100644 index 00000000..78669c4f --- /dev/null +++ b/test/bdd/api/search/queries.feature @@ -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 | diff --git a/test/bdd/website/search/simple.feature b/test/bdd/api/search/simple.feature similarity index 100% rename from test/bdd/website/search/simple.feature rename to test/bdd/api/search/simple.feature diff --git a/test/bdd/steps/queries.py b/test/bdd/steps/queries.py index c62b8a57..d0cda774 100644 --- a/test/bdd/steps/queries.py +++ b/test/bdd/steps/queries.py @@ -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.*)"(?P 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\S+ )?search query "(?P.*)"') -def website_search_request(context, fmt, query): +@when(u'sending (?P\S+ )?search query "(?P.*)"(?P 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\d+) has (?Pnot )?attributes (?P.*)') +@then(u'result (?P\d+ )?has (?Pnot )?attributes (?P.*)') 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\d+) has(?P no)? types (?P.*)') +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\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\d+ )?has bounding box in (?P[\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 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"