]> git.openstreetmap.org Git - nominatim.git/commitdiff
more API search tests
authorSarah Hoffmann <lonvia@denofr.de>
Mon, 19 Dec 2016 20:38:42 +0000 (21:38 +0100)
committerSarah Hoffmann <lonvia@denofr.de>
Fri, 30 Dec 2016 21:58:58 +0000 (22:58 +0100)
also move directory name back to api

test/bdd/api/search/params.feature [new file with mode: 0644]
test/bdd/api/search/queries.feature [new file with mode: 0644]
test/bdd/api/search/simple.feature [moved from test/bdd/website/search/simple.feature with 100% similarity]
test/bdd/steps/queries.py

diff --git a/test/bdd/api/search/params.feature b/test/bdd/api/search/params.feature
new file mode 100644 (file)
index 0000000..6590737
--- /dev/null
@@ -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 |
diff --git a/test/bdd/api/search/queries.feature b/test/bdd/api/search/queries.feature
new file mode 100644 (file)
index 0000000..78669c4
--- /dev/null
@@ -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 |
index c62b8a5773021d886e9f3b4b7d756cc6b99f1778..d0cda77469bab1559260e3af3b6f3529ffdfa775 100644 (file)
@@ -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"