replace behave BDD API tests with pytest-bdd tests

This commit is contained in:
Sarah Hoffmann
2025-03-18 11:19:06 +01:00
parent 50d4b0a386
commit 6959577aa4
51 changed files with 2975 additions and 2132 deletions

View File

@@ -27,7 +27,8 @@ lint:
flake8 src test/python test/bdd
bdd:
cd test/bdd; behave -DREMOVE_TEMPLATE=1
pytest test/bdd
cd test/bdd; behave -DREMOVE_TEMPLATE=1 db osm2pgsql
# Documentation

View File

@@ -1,63 +0,0 @@
@SQLITE
@APIDB
Feature: Localization of search results
Scenario: default language
When sending details query for R1155955
Then results contain
| ID | localname |
| 0 | Liechtenstein |
Scenario: accept-language first
When sending details query for R1155955
| accept-language |
| zh,de |
Then results contain
| ID | localname |
| 0 | |
Scenario: accept-language missing
When sending details query for R1155955
| accept-language |
| xx,fr,en,de |
Then results contain
| ID | localname |
| 0 | Liechtenstein |
Scenario: http accept language header first
Given the HTTP header
| accept-language |
| fo;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending details query for R1155955
Then results contain
| ID | localname |
| 0 | Liktinstein |
Scenario: http accept language header and accept-language
Given the HTTP header
| accept-language |
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending details query for R1155955
| accept-language |
| fo,en |
Then results contain
| ID | localname |
| 0 | Liktinstein |
Scenario: http accept language header fallback
Given the HTTP header
| accept-language |
| fo-ca,en-ca;q=0.5 |
When sending details query for R1155955
Then results contain
| ID | localname |
| 0 | Liktinstein |
Scenario: http accept language header fallback (upper case)
Given the HTTP header
| accept-language |
| fo-FR;q=0.8,en-ca;q=0.5 |
When sending details query for R1155955
Then results contain
| ID | localname |
| 0 | Liktinstein |

View File

@@ -1,96 +0,0 @@
@APIDB
Feature: Object details
Testing different parameter options for details API.
@SQLITE
Scenario: JSON Details
When sending json details query for W297699560
Then the result is valid json
And result has attributes geometry
And result has not attributes keywords,address,linked_places,parentof
And results contain in field geometry
| type |
| Point |
@SQLITE
Scenario: JSON Details with pretty printing
When sending json details query for W297699560
| pretty |
| 1 |
Then the result is valid json
And result has attributes geometry
And result has not attributes keywords,address,linked_places,parentof
@SQLITE
Scenario: JSON Details with addressdetails
When sending json details query for W297699560
| addressdetails |
| 1 |
Then the result is valid json
And result has attributes address
@SQLITE
Scenario: JSON Details with linkedplaces
When sending json details query for R123924
| linkedplaces |
| 1 |
Then the result is valid json
And result has attributes linked_places
@SQLITE
Scenario: JSON Details with hierarchy
When sending json details query for W297699560
| hierarchy |
| 1 |
Then the result is valid json
And result has attributes hierarchy
@SQLITE
Scenario: JSON Details with grouped hierarchy
When sending json details query for W297699560
| hierarchy | group_hierarchy |
| 1 | 1 |
Then the result is valid json
And result has attributes hierarchy
Scenario Outline: JSON Details with keywords
When sending json details query for <osmid>
| keywords |
| 1 |
Then the result is valid json
And result has attributes keywords
Examples:
| osmid |
| W297699560 |
| W243055645 |
| W243055716 |
| W43327921 |
# ticket #1343
Scenario: Details of a country with keywords
When sending details query for R1155955
| keywords |
| 1 |
Then the result is valid json
And result has attributes keywords
@SQLITE
Scenario Outline: JSON details with full geometry
When sending json details query for <osmid>
| polygon_geojson |
| 1 |
Then the result is valid json
And result has attributes geometry
And results contain in field geometry
| type |
| <geometry> |
Examples:
| osmid | geometry |
| W297699560 | LineString |
| W243055645 | Polygon |
| W243055716 | Polygon |
| W43327921 | LineString |

View File

@@ -1,81 +0,0 @@
@SQLITE
@APIDB
Feature: Object details
Check details page for correctness
Scenario Outline: Details via OSM id
When sending details query for <type><id>
Then the result is valid json
And results contain
| osm_type | osm_id |
| <type> | <id> |
Examples:
| type | id |
| N | 5484325405 |
| W | 43327921 |
| R | 123924 |
Scenario Outline: Details for different class types for the same OSM id
When sending details query for N300209696:<class>
Then the result is valid json
And results contain
| osm_type | osm_id | category |
| N | 300209696 | <class> |
Examples:
| class |
| tourism |
| mountain_pass |
Scenario Outline: Details via unknown OSM id
When sending details query for <object>
Then a HTTP 404 is returned
Examples:
| object |
| 1 |
| R1 |
| N300209696:highway |
Scenario: Details for interpolation way return the interpolation
When sending details query for W1
Then the result is valid json
And results contain
| category | type | osm_type | osm_id | admin_level |
| place | houses | W | 1 | 15 |
@Fail
Scenario: Details for interpolation way return the interpolation
When sending details query for 112871
Then the result is valid json
And results contain
| category | type | admin_level |
| place | houses | 15 |
And result has not attributes osm_type,osm_id
@Fail
Scenario: Details for interpolation way return the interpolation
When sending details query for 112820
Then the result is valid json
And results contain
| category | type | admin_level |
| place | postcode | 15 |
And result has not attributes osm_type,osm_id
Scenario Outline: Details debug output returns no errors
When sending debug details query for <feature>
Then the result is valid html
Examples:
| feature |
| N5484325405 |
| W1 |
| 112820 |
| 112871 |

View File

@@ -1,14 +0,0 @@
@SQLITE
@APIDB
Feature: Places by osm_type and osm_id Tests
Simple tests for errors in various response formats.
Scenario Outline: Force error by providing too many ids
When sending <format> lookup query for N1,N2,N3,N4,N5,N6,N7,N8,N9,N10,N11,N12,N13,N14,N15,N16,N17,N18,N19,N20,N21,N22,N23,N24,N25,N26,N27,N28,N29,N30,N31,N32,N33,N34,N35,N36,N37,N38,N39,N40,N41,N42,N43,N44,N45,N46,N47,N48,N49,N50,N51
Then a <format> user error is returned
Examples:
| format |
| xml |
| json |
| geojson |

View File

@@ -1,42 +0,0 @@
@SQLITE
@APIDB
Feature: Places by osm_type and osm_id Tests
Simple tests for response format.
Scenario Outline: address lookup for existing node, way, relation
When sending <format> lookup query for N5484325405,W43327921,,R123924,X99,N0
Then the result is valid <outformat>
And exactly 3 results are returned
Examples:
| format | outformat |
| xml | xml |
| json | json |
| jsonv2 | json |
| geojson | geojson |
| geocodejson | geocodejson |
Scenario: address lookup for non-existing or invalid node, way, relation
When sending xml lookup query for X99,,N0,nN158845944,ABC,,W9
Then exactly 0 results are returned
Scenario Outline: Boundingbox is returned
When sending <format> lookup query for N5484325405,W43327921
Then exactly 2 results are returned
And result 0 has bounding box in 47.135,47.14,9.52,9.525
And result 1 has bounding box in 47.07,47.08,9.50,9.52
Examples:
| format |
| json |
| jsonv2 |
| geojson |
| xml |
Scenario: Lookup of a linked place
When sending geocodejson lookup query for N1932181216
Then exactly 1 result is returned
And results contain
| name |
| Vaduz |

View File

@@ -1,45 +0,0 @@
@SQLITE
@APIDB
Feature: Geometries for reverse geocoding
Tests for returning geometries with reverse
Scenario: Polygons are returned fully by default
When sending v1/reverse at 47.13803,9.52264
| polygon_text |
| 1 |
Then results contain
| geotext |
| ^POLYGON\(\(9.5225302 47.138066, ?9.5225348 47.1379282, ?9.5226142 47.1379294, ?9.5226143 47.1379257, ?9.522615 47.137917, ?9.5226225 47.1379098, ?9.5226334 47.1379052, ?9.5226461 47.1379037, ?9.5226588 47.1379056, ?9.5226693 47.1379107, ?9.5226762 47.1379181, ?9.5226762 47.1379268, ?9.5226761 47.1379308, ?9.5227366 47.1379317, ?9.5227352 47.1379753, ?9.5227608 47.1379757, ?9.5227595 47.1380148, ?9.5227355 47.1380145, ?9.5227337 47.1380692, ?9.5225302 47.138066\)\) |
Scenario: Polygons can be slightly simplified
When sending v1/reverse at 47.13803,9.52264
| polygon_text | polygon_threshold |
| 1 | 0.00001 |
Then results contain
| geotext |
| ^POLYGON\(\(9.5225302 47.138066, ?9.5225348 47.1379282, ?9.5226142 47.1379294, ?9.5226225 47.1379098, ?9.5226588 47.1379056, ?9.5226761 47.1379308, ?9.5227366 47.1379317, ?9.5227352 47.1379753, ?9.5227608 47.1379757, ?9.5227595 47.1380148, ?9.5227355 47.1380145, ?9.5227337 47.1380692, ?9.5225302 47.138066\)\) |
Scenario: Polygons can be much simplified
When sending v1/reverse at 47.13803,9.52264
| polygon_text | polygon_threshold |
| 1 | 0.9 |
Then results contain
| geotext |
| ^POLYGON\(\([0-9. ]+, ?[0-9. ]+, ?[0-9. ]+, ?[0-9. ]+(, ?[0-9. ]+)?\)\) |
Scenario: For polygons return the centroid as center point
When sending v1/reverse at 47.13836,9.52304
Then results contain
| centroid |
| 9.52271080 47.13818045 |
Scenario: For streets return the closest point as center point
When sending v1/reverse at 47.13368,9.52942
Then results contain
| centroid |
| 9.529431527 47.13368172 |

View File

@@ -1,37 +0,0 @@
@SQLITE
@APIDB
Feature: Localization of reverse search results
Scenario: default language
When sending v1/reverse at 47.14,9.55
Then result addresses contain
| ID | country |
| 0 | Liechtenstein |
Scenario: accept-language parameter
When sending v1/reverse at 47.14,9.55
| accept-language |
| ja,en |
Then result addresses contain
| ID | country |
| 0 | |
Scenario: HTTP accept language header
Given the HTTP header
| accept-language |
| fo-ca,fo;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending v1/reverse at 47.14,9.55
Then result addresses contain
| ID | country |
| 0 | Liktinstein |
Scenario: accept-language parameter and HTTP header
Given the HTTP header
| accept-language |
| fo-ca,fo;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending v1/reverse at 47.14,9.55
| accept-language |
| en |
Then result addresses contain
| ID | country |
| 0 | Liechtenstein |

View File

@@ -1,117 +0,0 @@
@SQLITE
@APIDB
Feature: Reverse geocoding
Testing the reverse function
Scenario Outline: Simple reverse-geocoding with no results
When sending v1/reverse at <lat>,<lon>
Then exactly 0 results are returned
Examples:
| lat | lon |
| 0.0 | 0.0 |
| 91.3 | 0.4 |
| -700 | 0.4 |
| 0.2 | 324.44 |
| 0.2 | -180.4 |
Scenario: Unknown countries fall back to default country grid
When sending v1/reverse at 45.174,-103.072
Then results contain
| category | type | display_name |
| place | country | United States |
@Tiger
Scenario: TIGER house number
When sending v1/reverse at 32.4752389363,-86.4810198619
Then results contain
| category | type |
| place | house |
And result addresses contain
| house_number | road | postcode | country_code |
| 707 | Upper Kingston Road | 36067 | us |
@Tiger
Scenario: No TIGER house number for zoom < 18
When sending v1/reverse at 32.4752389363,-86.4810198619
| zoom |
| 17 |
Then results contain
| osm_type | category |
| way | highway |
And result addresses contain
| road | postcode | country_code |
| Upper Kingston Road | 36067 | us |
Scenario: Interpolated house number
When sending v1/reverse at 47.118533,9.57056562
Then results contain
| osm_type | category | type |
| way | place | house |
And result addresses contain
| house_number | road |
| 1019 | Grosssteg |
Scenario: Address with non-numerical house number
When sending v1/reverse at 47.107465,9.52838521614
Then result addresses contain
| house_number | road |
| 39A/B | Dorfstrasse |
Scenario: Address with numerical house number
When sending v1/reverse at 47.168440329479594,9.511551699184338
Then result addresses contain
| house_number | road |
| 6 | Schmedgässle |
Scenario Outline: Zoom levels below 5 result in country
When sending v1/reverse at 47.16,9.51
| zoom |
| <zoom> |
Then results contain
| display_name |
| Liechtenstein |
Examples:
| zoom |
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
Scenario: When on a street, the closest interpolation is shown
When sending v1/reverse at 47.118457166193245,9.570678289621355
| zoom |
| 18 |
Then results contain
| display_name |
| 1021, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
# github 2214
Scenario: Interpolations do not override house numbers when they are closer
When sending v1/reverse at 47.11778,9.57255
| zoom |
| 18 |
Then results contain
| display_name |
| 5, Grosssteg, Steg, Triesenberg, Oberland, 9497, Liechtenstein |
Scenario: Interpolations do not override house numbers when they are closer (2)
When sending v1/reverse at 47.11834,9.57167
| zoom |
| 18 |
Then results contain
| display_name |
| 3, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
Scenario: When on a street with zoom 18, the closest housenumber is returned
When sending v1/reverse at 47.11755503977281,9.572722250405036
| zoom |
| 18 |
Then result addresses contain
| house_number |
| 7 |

View File

@@ -1,107 +0,0 @@
@SQLITE
@APIDB
Feature: Geocodejson for Reverse API
Testing correctness of geocodejson output (API version v1).
Scenario Outline: Simple OSM result
When sending v1/reverse at 47.066,9.504 with format geocodejson
| addressdetails |
| <has_address> |
Then result has attributes place_id, accuracy
And result has <attributes> country,postcode,county,city,district,street,housenumber, admin
Then results contain
| osm_type | osm_id | osm_key | osm_value | type |
| node | 6522627624 | shop | bakery | house |
And results contain
| name | label |
| Dorfbäckerei Herrmann | Dorfbäckerei Herrmann, 29, Gnetsch, Mäls, Balzers, Oberland, 9496, Liechtenstein |
And results contain in field geojson
| type | coordinates |
| Point | [9.5036065, 47.0660892] |
And results contain in field __geocoding
| version | licence | attribution |
| 0.1.0 | ODbL | ^Data © OpenStreetMap contributors, ODbL 1.0. https?://osm.org/copyright$ |
Examples:
| has_address | attributes |
| 1 | attributes |
| 0 | not attributes |
Scenario: City housenumber-level address with street
When sending v1/reverse at 47.1068011,9.52810091 with format geocodejson
Then results contain
| housenumber | street | postcode | city | country |
| 8 | Im Winkel | 9495 | Triesen | Liechtenstein |
And results contain in field admin
| level6 | level8 |
| Oberland | Triesen |
Scenario: Town street-level address with street
When sending v1/reverse at 47.066,9.504 with format geocodejson
| zoom |
| 16 |
Then results contain
| name | city | postcode | country |
| Gnetsch | Balzers | 9496 | Liechtenstein |
Scenario: Poi street-level address with footway
When sending v1/reverse at 47.06515,9.50083 with format geocodejson
Then results contain
| street | city | postcode | country |
| Burgweg | Balzers | 9496 | Liechtenstein |
Scenario: City address with suburb
When sending v1/reverse at 47.146861,9.511771 with format geocodejson
Then results contain
| housenumber | street | district | city | postcode | country |
| 5 | Lochgass | Ebenholz | Vaduz | 9490 | Liechtenstein |
@Tiger
Scenario: Tiger address
When sending v1/reverse at 32.4752389363,-86.4810198619 with format geocodejson
Then results contain
| osm_type | osm_id | osm_key | osm_value | type |
| way | 396009653 | place | house | house |
And results contain
| housenumber | street | city | county | postcode | country |
| 707 | Upper Kingston Road | Prattville | Autauga County | 36067 | United States |
Scenario: Interpolation address
When sending v1/reverse at 47.118533,9.57056562 with format geocodejson
Then results contain
| osm_type | osm_id | osm_key | osm_value | type |
| way | 1 | place | house | house |
And results contain
| label |
| 1019, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
And result has not attributes name
Scenario: Line geometry output is supported
When sending v1/reverse at 47.06597,9.50467 with format geocodejson
| param | value |
| polygon_geojson | 1 |
Then results contain in field geojson
| type |
| LineString |
Scenario Outline: Only geojson polygons are supported
When sending v1/reverse at 47.06597,9.50467 with format geocodejson
| param | value |
| <param> | 1 |
Then results contain in field geojson
| type |
| Point |
Examples:
| param |
| polygon_text |
| polygon_svg |
| polygon_kml |

View File

@@ -1,73 +0,0 @@
@SQLITE
@APIDB
Feature: Geojson for Reverse API
Testing correctness of geojson output (API version v1).
Scenario Outline: Simple OSM result
When sending v1/reverse at 47.066,9.504 with format geojson
| addressdetails |
| <has_address> |
Then result has attributes place_id, importance, __licence
And result has <attributes> address
And results contain
| osm_type | osm_id | place_rank | category | type | addresstype |
| node | 6522627624 | 30 | shop | bakery | shop |
And results contain
| name | display_name |
| Dorfbäckerei Herrmann | Dorfbäckerei Herrmann, 29, Gnetsch, Mäls, Balzers, Oberland, 9496, Liechtenstein |
And results contain
| boundingbox |
| [47.0660392, 47.0661392, 9.5035565, 9.5036565] |
And results contain in field geojson
| type | coordinates |
| Point | [9.5036065, 47.0660892] |
Examples:
| has_address | attributes |
| 1 | attributes |
| 0 | not attributes |
@Tiger
Scenario: Tiger address
When sending v1/reverse at 32.4752389363,-86.4810198619 with format geojson
Then results contain
| osm_type | osm_id | category | type | addresstype | place_rank |
| way | 396009653 | place | house | place | 30 |
Scenario: Interpolation address
When sending v1/reverse at 47.118533,9.57056562 with format geojson
Then results contain
| osm_type | osm_id | place_rank | category | type | addresstype |
| way | 1 | 30 | place | house | place |
And results contain
| boundingbox |
| ^\[47.118495\d*, 47.118595\d*, 9.570496\d*, 9.570596\d*\] |
And results contain
| display_name |
| 1019, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
Scenario: Line geometry output is supported
When sending v1/reverse at 47.06597,9.50467 with format geojson
| param | value |
| polygon_geojson | 1 |
Then results contain in field geojson
| type |
| LineString |
Scenario Outline: Only geojson polygons are supported
When sending v1/reverse at 47.06597,9.50467 with format geojson
| param | value |
| <param> | 1 |
Then results contain in field geojson
| type |
| Point |
Examples:
| param |
| polygon_text |
| polygon_svg |
| polygon_kml |

View File

@@ -1,130 +0,0 @@
@SQLITE
@APIDB
Feature: Json output for Reverse API
Testing correctness of json and jsonv2 output (API version v1).
Scenario Outline: OSM result with and without addresses
When sending v1/reverse at 47.066,9.504 with format json
| addressdetails |
| <has_address> |
Then result has <attributes> address
When sending v1/reverse at 47.066,9.504 with format jsonv2
| addressdetails |
| <has_address> |
Then result has <attributes> address
Examples:
| has_address | attributes |
| 1 | attributes |
| 0 | not attributes |
Scenario Outline: Simple OSM result
When sending v1/reverse at 47.066,9.504 with format <format>
Then result has attributes place_id
And results contain
| licence |
| ^Data © OpenStreetMap contributors, ODbL 1.0. https?://osm.org/copyright$ |
And results contain
| osm_type | osm_id |
| node | 6522627624 |
And results contain
| centroid | boundingbox |
| 9.5036065 47.0660892 | ['47.0660392', '47.0661392', '9.5035565', '9.5036565'] |
And results contain
| display_name |
| Dorfbäckerei Herrmann, 29, Gnetsch, Mäls, Balzers, Oberland, 9496, Liechtenstein |
And result has not attributes namedetails,extratags
Examples:
| format |
| json |
| jsonv2 |
Scenario: Extra attributes of jsonv2 result
When sending v1/reverse at 47.066,9.504 with format jsonv2
Then result has attributes importance
Then results contain
| category | type | name | place_rank | addresstype |
| shop | bakery | Dorfbäckerei Herrmann | 30 | shop |
@Tiger
Scenario: Tiger address
When sending v1/reverse at 32.4752389363,-86.4810198619 with format jsonv2
Then results contain
| osm_type | osm_id | category | type | addresstype |
| way | 396009653 | place | house | place |
Scenario Outline: Interpolation address
When sending v1/reverse at 47.118533,9.57056562 with format <format>
Then results contain
| osm_type | osm_id |
| way | 1 |
And results contain
| centroid | boundingbox |
| 9.57054676 47.118545392 | ^\['47.118495\d*', '47.118595\d*', '9.570496\d*', '9.570596\d*'\] |
And results contain
| display_name |
| 1019, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
Examples:
| format |
| json |
| jsonv2 |
Scenario Outline: Output of geojson
When sending v1/reverse at 47.06597,9.50467 with format <format>
| param | value |
| polygon_geojson | 1 |
Then results contain in field geojson
| type | coordinates |
| LineString | [[9.5039353, 47.0657546], [9.5040437, 47.0657781], [9.5040808, 47.065787], [9.5054298, 47.0661407]] |
Examples:
| format |
| json |
| jsonv2 |
Scenario Outline: Output of WKT
When sending v1/reverse at 47.06597,9.50467 with format <format>
| param | value |
| polygon_text | 1 |
Then results contain
| geotext |
| ^LINESTRING\(9.5039353 47.0657546, ?9.5040437 47.0657781, ?9.5040808 47.065787, ?9.5054298 47.0661407\) |
Examples:
| format |
| json |
| jsonv2 |
Scenario Outline: Output of SVG
When sending v1/reverse at 47.06597,9.50467 with format <format>
| param | value |
| polygon_svg | 1 |
Then results contain
| svg |
| M 9.5039353 -47.0657546 L 9.5040437 -47.0657781 9.5040808 -47.065787 9.5054298 -47.0661407 |
Examples:
| format |
| json |
| jsonv2 |
Scenario Outline: Output of KML
When sending v1/reverse at 47.06597,9.50467 with format <format>
| param | value |
| polygon_kml | 1 |
Then results contain
| geokml |
| ^<LineString><coordinates>9.5039\d*,47.0657\d* 9.5040\d*,47.0657\d* 9.5040\d*,47.065\d* 9.5054\d*,47.0661\d*</coordinates></LineString> |
Examples:
| format |
| json |
| jsonv2 |

View File

@@ -1,206 +0,0 @@
@SQLITE
@APIDB
Feature: v1/reverse Parameter Tests
Tests for parameter inputs for the v1 reverse endpoint.
This file contains mostly bad parameter input. Valid parameters
are tested in the format tests.
Scenario: Bad format
When sending v1/reverse at 47.14122383,9.52169581334 with format sdf
Then a HTTP 400 is returned
Scenario: Missing lon parameter
When sending v1/reverse at 52.52,
Then a HTTP 400 is returned
Scenario: Missing lat parameter
When sending v1/reverse at ,52.52
Then a HTTP 400 is returned
Scenario Outline: Bad format for lat or lon
When sending v1/reverse at ,
| lat | lon |
| <lat> | <lon> |
Then a HTTP 400 is returned
Examples:
| lat | lon |
| 48.9660 | 8,4482 |
| 48,9660 | 8.4482 |
| 48,9660 | 8,4482 |
| 48.966.0 | 8.4482 |
| 48.966 | 8.448.2 |
| Nan | 8.448 |
| 48.966 | Nan |
| Inf | 5.6 |
| 5.6 | -Inf |
| <script></script> | 3.4 |
| 3.4 | <script></script> |
| -45.3 | ; |
| gkjd | 50 |
Scenario: Non-numerical zoom levels return an error
When sending v1/reverse at 47.14122383,9.52169581334
| zoom |
| adfe |
Then a HTTP 400 is returned
Scenario Outline: Truthy values for boolean parameters
When sending v1/reverse at 47.14122383,9.52169581334
| addressdetails |
| <value> |
Then exactly 1 result is returned
And result has attributes address
When sending v1/reverse at 47.14122383,9.52169581334
| extratags |
| <value> |
Then exactly 1 result is returned
And result has attributes extratags
When sending v1/reverse at 47.14122383,9.52169581334
| namedetails |
| <value> |
Then exactly 1 result is returned
And result has attributes namedetails
When sending v1/reverse at 47.14122383,9.52169581334
| polygon_geojson |
| <value> |
Then exactly 1 result is returned
And result has attributes geojson
When sending v1/reverse at 47.14122383,9.52169581334
| polygon_kml |
| <value> |
Then exactly 1 result is returned
And result has attributes geokml
When sending v1/reverse at 47.14122383,9.52169581334
| polygon_svg |
| <value> |
Then exactly 1 result is returned
And result has attributes svg
When sending v1/reverse at 47.14122383,9.52169581334
| polygon_text |
| <value> |
Then exactly 1 result is returned
And result has attributes geotext
Examples:
| value |
| yes |
| no |
| -1 |
| 100 |
| false |
| 00 |
Scenario: Only one geometry can be requested
When sending v1/reverse at 47.165989816710066,9.515774846076965
| polygon_text | polygon_svg |
| 1 | 1 |
Then a HTTP 400 is returned
Scenario Outline: Wrapping of legal jsonp requests
When sending v1/reverse at 67.3245,0.456 with format <format>
| json_callback |
| foo |
Then the result is valid <outformat>
Examples:
| format | outformat |
| json | json |
| jsonv2 | json |
| geojson | geojson |
| geocodejson | geocodejson |
Scenario Outline: Illegal jsonp are not allowed
When sending v1/reverse at 47.165989816710066,9.515774846076965
| param | value |
|json_callback | <data> |
Then a HTTP 400 is returned
Examples:
| data |
| 1asd |
| bar(foo) |
| XXX['bad'] |
| foo; evil |
Scenario Outline: Reverse debug mode produces valid HTML
When sending v1/reverse at , with format debug
| lat | lon |
| <lat> | <lon> |
Then the result is valid html
Examples:
| lat | lon |
| 0.0 | 0.0 |
| 47.06645 | 9.56601 |
| 47.14081 | 9.52267 |
Scenario Outline: Full address display for city housenumber-level address with street
When sending v1/reverse at 47.1068011,9.52810091 with format <format>
Then address of result 0 is
| type | value |
| house_number | 8 |
| road | Im Winkel |
| neighbourhood | Oberdorf |
| village | Triesen |
| ISO3166-2-lvl8 | LI-09 |
| county | Oberland |
| postcode | 9495 |
| country | Liechtenstein |
| country_code | li |
Examples:
| format |
| json |
| jsonv2 |
| geojson |
| xml |
Scenario Outline: Results with name details
When sending v1/reverse at 47.14052,9.52202 with format <format>
| zoom | namedetails |
| 14 | 1 |
Then results contain in field namedetails
| name |
| Ebenholz |
Examples:
| format |
| json |
| jsonv2 |
| xml |
| geojson |
Scenario Outline: Results with extratags
When sending v1/reverse at 47.14052,9.52202 with format <format>
| zoom | extratags |
| 14 | 1 |
Then results contain in field extratags
| wikidata |
| Q4529531 |
Examples:
| format |
| json |
| jsonv2 |
| xml |
| geojson |

View File

@@ -1,88 +0,0 @@
@SQLITE
@APIDB
Feature: XML output for Reverse API
Testing correctness of xml output (API version v1).
Scenario Outline: OSM result with and without addresses
When sending v1/reverse at 47.066,9.504 with format xml
| addressdetails |
| <has_address> |
Then result has attributes place_id
Then result has <attributes> address
And results contain
| osm_type | osm_id | place_rank | address_rank |
| node | 6522627624 | 30 | 30 |
And results contain
| centroid | boundingbox |
| 9.5036065 47.0660892 | 47.0660392,47.0661392,9.5035565,9.5036565 |
And results contain
| ref | display_name |
| Dorfbäckerei Herrmann | Dorfbäckerei Herrmann, 29, Gnetsch, Mäls, Balzers, Oberland, 9496, Liechtenstein |
Examples:
| has_address | attributes |
| 1 | attributes |
| 0 | not attributes |
@Tiger
Scenario: Tiger address
When sending v1/reverse at 32.4752389363,-86.4810198619 with format xml
Then results contain
| osm_type | osm_id | place_rank | address_rank |
| way | 396009653 | 30 | 30 |
And results contain
| centroid | boundingbox |
| -86.4808553 32.4753580 | ^32.4753080\d*,32.4754080\d*,-86.4809053\d*,-86.4808053\d* |
And results contain
| display_name |
| 707, Upper Kingston Road, Upper Kingston, Prattville, Autauga County, 36067, United States |
Scenario: Interpolation address
When sending v1/reverse at 47.118533,9.57056562 with format xml
Then results contain
| osm_type | osm_id | place_rank | address_rank |
| way | 1 | 30 | 30 |
And results contain
| centroid | boundingbox |
| 9.57054676 47.118545392 | ^47.118495\d*,47.118595\d*,9.570496\d*,9.570596\d* |
And results contain
| display_name |
| 1019, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
Scenario: Output of geojson
When sending v1/reverse at 47.06597,9.50467 with format xml
| param | value |
| polygon_geojson | 1 |
Then results contain
| geojson |
| {"type":"LineString","coordinates":[[9.5039353,47.0657546],[9.5040437,47.0657781],[9.5040808,47.065787],[9.5054298,47.0661407]]} |
Scenario: Output of WKT
When sending v1/reverse at 47.06597,9.50467 with format xml
| param | value |
| polygon_text | 1 |
Then results contain
| geotext |
| ^LINESTRING\(9.5039353 47.0657546, ?9.5040437 47.0657781, ?9.5040808 47.065787, ?9.5054298 47.0661407\) |
Scenario: Output of SVG
When sending v1/reverse at 47.06597,9.50467 with format xml
| param | value |
| polygon_svg | 1 |
Then results contain
| geosvg |
| M 9.5039353 -47.0657546 L 9.5040437 -47.0657781 9.5040808 -47.065787 9.5054298 -47.0661407 |
Scenario: Output of KML
When sending v1/reverse at 47.06597,9.50467 with format xml
| param | value |
| polygon_kml | 1 |
Then results contain
| geokml |
| ^<geokml><LineString><coordinates>9.5039\d*,47.0657\d* 9.5040\d*,47.0657\d* 9.5040\d*,47.065\d* 9.5054\d*,47.0661\d*</coordinates></LineString></geokml> |

View File

@@ -1,28 +0,0 @@
@SQLITE
@APIDB
Feature: Parameters for Search API
Testing correctness of geocodejson output.
Scenario: City housenumber-level address with street
When sending geocodejson search query "Im Winkel 8, Triesen" with address
Then results contain
| housenumber | street | postcode | city | country |
| 8 | Im Winkel | 9495 | Triesen | Liechtenstein |
Scenario: Town street-level address with street
When sending geocodejson search query "Gnetsch, Balzers" with address
Then results contain
| name | city | postcode | country |
| Gnetsch | Balzers | 9496 | Liechtenstein |
Scenario: Town street-level address with footway
When sending geocodejson search query "burg gutenberg 6000 jahre geschichte" with address
Then results contain
| street | city | postcode | country |
| Burgweg | Balzers | 9496 | Liechtenstein |
Scenario: City address with suburb
When sending geocodejson search query "Lochgass 5, Ebenholz, Vaduz" with address
Then results contain
| housenumber | street | district | city | postcode | country |
| 5 | Lochgass | Ebenholz | Vaduz | 9490 | Liechtenstein |

View File

@@ -1,63 +0,0 @@
@SQLITE
@APIDB
Feature: Localization of search results
Scenario: default language
When sending json search query "Liechtenstein"
Then results contain
| ID | display_name |
| 0 | Liechtenstein |
Scenario: accept-language first
When sending json search query "Liechtenstein"
| accept-language |
| zh,de |
Then results contain
| ID | display_name |
| 0 | |
Scenario: accept-language missing
When sending json search query "Liechtenstein"
| accept-language |
| xx,fr,en,de |
Then results contain
| ID | display_name |
| 0 | Liechtenstein |
Scenario: http accept language header first
Given the HTTP header
| accept-language |
| fo;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending json search query "Liechtenstein"
Then results contain
| ID | display_name |
| 0 | Liktinstein |
Scenario: http accept language header and accept-language
Given the HTTP header
| accept-language |
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending json search query "Liechtenstein"
| accept-language |
| fo,en |
Then results contain
| ID | display_name |
| 0 | Liktinstein |
Scenario: http accept language header fallback
Given the HTTP header
| accept-language |
| fo-ca,en-ca;q=0.5 |
When sending json search query "Liechtenstein"
Then results contain
| ID | display_name |
| 0 | Liktinstein |
Scenario: http accept language header fallback (upper case)
Given the HTTP header
| accept-language |
| fo-FR;q=0.8,en-ca;q=0.5 |
When sending json search query "Liechtenstein"
Then results contain
| ID | display_name |
| 0 | Liktinstein |

View File

@@ -1,362 +0,0 @@
@SQLITE
@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
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,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: Unknown formats returns a user error
When sending search query "Vaduz"
| format |
| x45 |
Then a HTTP 400 is returned
Scenario Outline: Search with addressdetails
When sending <format> search query "Triesen" with address
Then address of result 0 is
| type | value |
| village | Triesen |
| county | Oberland |
| postcode | 9495 |
| country | Liechtenstein |
| country_code | li |
| ISO3166-2-lvl8 | LI-09 |
Examples:
| format |
| json |
| jsonv2 |
| geojson |
| xml |
Scenario: Coordinate search with addressdetails
When sending json search query "47.12400621,9.6047552"
| accept-language |
| en |
Then results contain
| display_name |
| Guschg, Valorschstrasse, Balzers, Oberland, 9497, Liechtenstein |
Scenario: Address details with unknown class types
When sending json search query "Kloster St. Elisabeth" with address
Then results contain
| ID | class | type |
| 0 | amenity | monastery |
And result addresses contain
| ID | amenity |
| 0 | Kloster St. Elisabeth |
Scenario: Disabling deduplication
When sending json search query "Malbunstr"
Then there are no duplicates
When sending json search query "Malbunstr"
| dedupe |
| 0 |
Then there are duplicates
Scenario: Search with bounded viewbox in right area
When sending json search query "post" with address
| bounded | viewbox |
| 1 | 9,47,10,48 |
Then result addresses contain
| ID | town |
| 0 | Vaduz |
When sending json search query "post" with address
| bounded | viewbox |
| 1 | 9.49712,47.17122,9.52605,47.16242 |
Then result addresses contain
| town |
| Schaan |
Scenario: Country search with bounded viewbox remain in the area
When sending json search query "" with address
| bounded | viewbox | country |
| 1 | 9.49712,47.17122,9.52605,47.16242 | de |
Then less than 1 result is returned
Scenario: Search with bounded viewboxlbrt in right area
When sending json search query "bar" with address
| bounded | viewboxlbrt |
| 1 | 9.49712,47.16242,9.52605,47.17122 |
Then result addresses contain
| town |
| Schaan |
@Fail
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.49712,47.17122,9.52605,47.16242 |
Then result has centroid in 9.49712,47.16242,9.52605,47.17122
Scenario: Prefer results within viewbox
When sending json search query "Gässle" with address
| accept-language | viewbox |
| en | 9.52413,47.10759,9.53140,47.10539 |
Then result addresses contain
| ID | village |
| 0 | Triesen |
When sending json search query "Gässle" with address
| accept-language | viewbox |
| en | 9.45949,47.08421,9.54094,47.05466 |
Then result addresses contain
| ID | town |
| 0 | Balzers |
Scenario: viewboxes cannot be points
When sending json search query "foo"
| viewbox |
| 1.01,34.6,1.01,34.6 |
Then a HTTP 400 is returned
Scenario Outline: viewbox must have four coordinate numbers
When sending json search query "foo"
| viewbox |
| <viewbox> |
Then a HTTP 400 is returned
Examples:
| viewbox |
| 34 |
| 0.003,-84.4 |
| 5.2,4.5542,12.4 |
| 23.1,-6,0.11,44.2,9.1 |
Scenario Outline: viewboxlbrt must have four coordinate numbers
When sending json search query "foo"
| viewboxlbrt |
| <viewbox> |
Then a HTTP 400 is returned
Examples:
| viewbox |
| 34 |
| 0.003,-84.4 |
| 5.2,4.5542,12.4 |
| 23.1,-6,0.11,44.2,9.1 |
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 "landstr"
| dedupe |
| 0 |
Then more than 4 results are returned
When sending json search query "landstr"
| limit | dedupe |
| 4 | 0 |
Then exactly 4 results are returned
Scenario: Limit parameter must be a number
When sending search query "Blue Laguna"
| limit |
| ); |
Then a HTTP 400 is returned
Scenario: Restrict to feature type country
When sending xml search query "fürstentum"
| featureType |
| country |
Then results contain
| place_rank |
| 4 |
Scenario: Restrict to feature type state
When sending xml search query "Wangerberg"
Then at least 1 result is returned
When sending xml search query "Wangerberg"
| featureType |
| state |
Then exactly 0 results are returned
Scenario: Restrict to feature type city
When sending xml search query "vaduz"
Then at least 1 result is returned
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 "Malbun"
Then results contain
| ID | class |
| 1 | landuse |
When sending json search query "Malbun"
| featureType |
| settlement |
Then results contain
| class | type |
| place | village |
Scenario Outline: Search with polygon threshold (json)
When sending json search query "triesenberg"
| 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 "triesenberg"
| 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 "triesenberg"
| 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 "Landstr"
| extratags |
| 1 |
Then result has attributes extratags
Examples:
| format |
| xml |
| json |
| jsonv2 |
| geojson |
Scenario Outline: Search with namedetails
When sending <format> search query "Landstr"
| namedetails |
| 1 |
Then result has attributes namedetails
Examples:
| format |
| xml |
| json |
| jsonv2 |
| geojson |
Scenario Outline: Search result with contains TEXT geometry
When sending <format> search query "triesenberg"
| polygon_text |
| 1 |
Then result has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geotext |
| json | geotext |
| jsonv2 | geotext |
Scenario Outline: Search result contains SVG geometry
When sending <format> search query "triesenberg"
| 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 "triesenberg"
| 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 "triesenberg"
| polygon_geojson |
| 1 |
Then result has attributes <response_attribute>
Examples:
| format | response_attribute |
| xml | geojson |
| json | geojson |
| jsonv2 | geojson |
| geojson | geojson |
Scenario Outline: Search result in geojson format contains no non-geojson geometry
When sending geojson search query "triesenberg"
| polygon_text | polygon_svg | polygon_geokml |
| 1 | 1 | 1 |
Then result 0 has not attributes <response_attribute>
Examples:
| response_attribute |
| geotext |
| polygonpoints |
| svg |
| geokml |
Scenario: Array parameters are ignored
When sending json search query "Vaduz" with address
| countrycodes[] | polygon_svg[] | limit[] | polygon_threshold[] |
| IT | 1 | 3 | 3.4 |
Then result addresses contain
| ID | country_code |
| 0 | li |

View File

@@ -1,221 +0,0 @@
@SQLITE
@APIDB
Feature: Search queries
Generic search result correctness
Scenario: Search for natural object
When sending json search query "Samina"
| accept-language |
| en |
Then results contain
| ID | class | type | display_name |
| 0 | waterway | river | Samina, Austria |
Scenario: House number search for non-street address
When sending json search query "6 Silum, Liechtenstein" with address
| accept-language |
| en |
Then address of result 0 is
| type | value |
| house_number | 6 |
| village | Silum |
| town | Triesenberg |
| county | Oberland |
| postcode | 9497 |
| country | Liechtenstein |
| country_code | li |
| ISO3166-2-lvl8 | LI-10 |
Scenario: House number interpolation
When sending json search query "Grosssteg 1023, Triesenberg" with address
| accept-language |
| de |
Then address of result 0 contains
| type | value |
| house_number | 1023 |
| road | Grosssteg |
| village | Sücka |
| postcode | 9497 |
| town | Triesenberg |
| country | Liechtenstein |
| country_code | li |
Scenario: With missing housenumber search falls back to road
When sending json search query "Bündaweg 555" with address
Then address of result 0 is
| type | value |
| road | Bündaweg |
| village | Silum |
| postcode | 9497 |
| county | Oberland |
| town | Triesenberg |
| country | Liechtenstein |
| country_code | li |
| ISO3166-2-lvl8 | LI-10 |
Scenario Outline: Housenumber 0 can be found
When sending <format> search query "Gnalpstrasse 0" with address
Then results contain
| display_name |
| ^0,.* |
And result addresses contain
| house_number |
| 0 |
Examples:
| format |
| xml |
| json |
| jsonv2 |
| geojson |
@Tiger
Scenario: TIGER house number
When sending json search query "697 Upper Kingston Road"
Then results contain
| osm_type | display_name |
| way | ^697,.* |
Scenario: Search with class-type feature
When sending jsonv2 search query "bars in ebenholz"
Then results contain
| place_rank |
| 30 |
Scenario: Search with specific amenity
When sending json search query "[restaurant] Vaduz" with address
Then result addresses contain
| country |
| Liechtenstein |
And results contain
| class | type |
| amenity | restaurant |
Scenario: Search with specific amenity also work in country
When sending json search query "restaurants in liechtenstein" with address
Then result addresses contain
| country |
| Liechtenstein |
And results contain
| class | type |
| amenity | restaurant |
Scenario: Search with key-value amenity
When sending json search query "[club=scout] Vaduz"
Then results contain
| class | type |
| club | scout |
Scenario: POI search near given coordinate
When sending json search query "restaurant near 47.16712,9.51100"
Then results contain
| class | type |
| amenity | restaurant |
Scenario: Arbitrary key/value search near given coordinate
When sending json search query "[leisure=firepit] 47.150° N 9.5340493° E"
Then results contain
| class | type |
| leisure | firepit |
Scenario: POI search in a bounded viewbox
When sending json search query "restaurants"
| viewbox | bounded |
| 9.50830,47.15253,9.52043,47.14866 | 1 |
Then results contain
| class | type |
| amenity | restaurant |
Scenario Outline: Key/value search near given coordinate can be restricted to country
When sending json search query "[natural=peak] 47.06512,9.53965" with address
| countrycodes |
| <cc> |
Then result addresses contain
| country_code |
| <cc> |
Examples:
| cc |
| li |
| ch |
Scenario: Name search near given coordinate
When sending json search query "sporry" with address
Then result addresses contain
| ID | town |
| 0 | Vaduz |
When sending json search query "sporry, 47.10791,9.52676" with address
Then result addresses contain
| ID | village |
| 0 | Triesen |
Scenario: Name search near given coordinate without result
When sending json search query "sporry, N 47 15 7 W 9 61 26"
Then exactly 0 results are returned
Scenario: Arbitrary key/value search near a road
When sending json search query "[amenity=drinking_water] Wissfläckaweg"
Then results contain
| class | type |
| amenity | drinking_water |
Scenario: Ignore other country codes in structured search with country
When sending json search query ""
| city | country |
| li | de |
Then exactly 0 results are returned
Scenario: Ignore country searches when query is restricted to countries
When sending json search query "fr"
| countrycodes |
| li |
Then exactly 0 results are returned
Scenario: Country searches only return results for the given country
When sending search query "Ans Trail" with address
| countrycodes |
| li |
Then result addresses contain
| country_code |
| li |
# https://trac.openstreetmap.org/ticket/5094
Scenario: housenumbers are ordered by complete match first
When sending json search query "Austrasse 11, Vaduz" with address
Then result addresses contain
| ID | house_number |
| 0 | 11 |
Scenario Outline: Coordinate searches with white spaces
When sending json search query "<data>"
Then exactly 1 result is returned
And results contain
| class |
| water |
Examples:
| data |
| sporry weiher, N 47.10791° E 9.52676° |
| sporry weiher, N 47.10791° E 9.52676° |
| sporry weiher , N 47.10791° E 9.52676° |
| sporry weiher, N 47.10791° E 9.52676° |
| sporry weiher , N 47.10791° E 9.52676° |
Scenario: Searches with white spaces
When sending json search query "52 Bodastr , Triesenberg"
Then results contain
| class | type |
| highway | residential |
# github #1949
Scenario: Addressdetails always return the place type
When sending json search query "Vaduz" with address
Then result addresses contain
| ID | town |
| 0 | Vaduz |
Scenario: Search can handle complex query word sets
When sending search query "aussenstelle universitat lichtenstein wachterhaus aussenstelle universitat lichtenstein wachterhaus aussenstelle universitat lichtenstein wachterhaus aussenstelle universitat lichtenstein wachterhaus"
Then a HTTP 200 is returned

View File

@@ -1,208 +0,0 @@
@SQLITE
@APIDB
Feature: Simple Tests
Simple tests for internal server errors and response format.
Scenario Outline: Testing different parameters
When sending search query "Vaduz"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
When sending xml search query "Vaduz"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
When sending json search query "Vaduz"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
When sending jsonv2 search query "Vaduz"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
When sending geojson search query "Vaduz"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
When sending geocodejson search query "Vaduz"
| param | value |
| <parameter> | <value> |
Then at least 1 result is returned
Examples:
| parameter | value |
| addressdetails | 0 |
| polygon_text | 0 |
| polygon_kml | 0 |
| polygon_geojson | 0 |
| polygon_svg | 0 |
| accept-language | de,en |
| countrycodes | li |
| bounded | 1 |
| bounded | 0 |
| exclude_place_ids| 385252,1234515 |
| limit | 1000 |
| dedupe | 1 |
| dedupe | 0 |
| extratags | 0 |
| namedetails | 0 |
Scenario: Search with invalid output format
When sending search query "Berlin"
| format |
| fd$# |
Then a HTTP 400 is returned
Scenario Outline: Simple Searches
When sending search query "<query>"
Then the result is valid json
When sending xml search query "<query>"
Then the result is valid xml
When sending json search query "<query>"
Then the result is valid json
When sending jsonv2 search query "<query>"
Then the result is valid json
When sending geojson search query "<query>"
Then the result is valid geojson
Examples:
| query |
| New York, New York |
| France |
| 12, Main Street, Houston |
| München |
| |
| hotels in nantes |
| xywxkrf |
| gh; foo() |
| %#$@*&l;der#$! |
| 234 |
| 47.4,8.3 |
Scenario: Empty XML search
When sending xml search query "xnznxvcx"
Then result header contains
| attr | value |
| querystring | xnznxvcx |
| more_url | .*q=xnznxvcx.*format=xml |
Scenario: Empty XML search with special XML characters
When sending xml search query "xfdghn&zxn"xvbyx<vxx>cssdex"
Then result header contains
| attr | value |
| querystring | xfdghn&zxn"xvbyx<vxx>cssdex |
| more_url | .*q=xfdghn%26zxn%22xvbyx%3Cvxx%3Ecssdex.*format=xml |
Scenario: Empty XML search with viewbox
When sending xml search query "xnznxvcx"
| viewbox |
| 12,33,77,45.13 |
Then result header contains
| attr | value |
| querystring | xnznxvcx |
| viewbox | 12,33,77,45.13 |
Scenario: Empty XML search with viewboxlbrt
When sending xml search query "xnznxvcx"
| viewboxlbrt |
| 12,34.13,77,45 |
Then result header contains
| attr | value |
| querystring | xnznxvcx |
| viewbox | 12,34.13,77,45 |
Scenario: Empty XML search with viewboxlbrt and viewbox
When sending xml search query "pub"
| viewbox | viewboxblrt |
| 12,33,77,45.13 | 1,2,3,4 |
Then result header contains
| attr | value |
| querystring | pub |
| viewbox | 12,33,77,45.13 |
Scenario: Empty XML search with excluded place ids
When sending xml search query "jghrleoxsbwjer"
| exclude_place_ids |
| 123,76,342565 |
Then result header contains
| attr | value |
| exclude_place_ids | 123,76,342565 |
Scenario: Empty XML search with bad excluded place ids
When sending xml search query "jghrleoxsbwjer"
| exclude_place_ids |
| , |
Then result header has not attributes exclude_place_ids
Scenario Outline: Wrapping of legal jsonp search requests
When sending json search query "Tokyo"
| param | value |
|json_callback | <data> |
Then result header contains
| attr | value |
| json_func | <result> |
Examples:
| data | result |
| foo | foo |
| FOO | FOO |
| __world | __world |
Scenario Outline: Wrapping of illegal jsonp search requests
When sending json search query "Tokyo"
| param | value |
|json_callback | <data> |
Then a json user error is returned
Examples:
| data |
| 1asd |
| bar(foo) |
| XXX['bad'] |
| foo; evil |
Scenario: Ignore jsonp parameter for anything but json
When sending json search query "Malibu"
| json_callback |
| 234 |
Then a HTTP 400 is returned
When sending xml search query "Malibu"
| json_callback |
| 234 |
Then the result is valid xml
Scenario Outline: Empty search
When sending <format> search query "YHlERzzx"
Then exactly 0 results are returned
Examples:
| format |
| json |
| jsonv2 |
| geojson |
| geocodejson |
Scenario: Search for non-existing coordinates
When sending json search query "-21.0,-33.0"
Then exactly 0 results are returned
Scenario: Country code selection is retained in more URL (#596)
When sending xml search query "Vaduz"
| countrycodes |
| pl,1,,invalid,undefined,%3Cb%3E,bo,, |
Then result header contains
| attr | value |
| more_url | .*&countrycodes=pl%2Cbo&.* |
Scenario Outline: Search debug output does not return errors
When sending debug search query "<query>"
Then a HTTP 200 is returned
Examples:
| query |
| Liechtenstein |
| Triesen |
| Pfarrkirche |
| Landstr 27 Steinort, Triesenberg, 9495 |
| 9497 |
| restaurant in triesen |

View File

@@ -1,79 +0,0 @@
@SQLITE
@APIDB
Feature: Structured search queries
Testing correctness of results with
structured queries
Scenario: Country only
When sending json search query "" with address
| country |
| Liechtenstein |
Then address of result 0 is
| type | value |
| country | Liechtenstein |
| country_code | li |
Scenario: Postcode only
When sending json search query "" with address
| postalcode |
| 9495 |
Then results contain
| type |
| ^post(al_)?code |
And result addresses contain
| postcode |
| 9495 |
Scenario: Street, postcode and country
When sending xml search query "" with address
| street | postalcode | country |
| Old Palace Road | GU2 7UP | United Kingdom |
Then result header contains
| attr | value |
| querystring | Old Palace Road, GU2 7UP, United Kingdom |
Scenario: Street with housenumber, city and postcode
When sending xml search query "" with address
| street | city | postalcode |
| 19 Am schrägen Weg | Vaduz | 9490 |
Then result addresses contain
| house_number | road |
| 19 | Am Schrägen Weg |
Scenario: Street with housenumber, city and bad postcode
When sending xml search query "" with address
| street | city | postalcode |
| 19 Am schrägen Weg | Vaduz | 9491 |
Then result addresses contain
| house_number | road |
| 19 | Am Schrägen Weg |
Scenario: Amenity, city
When sending json search query "" with address
| city | amenity |
| Vaduz | bar |
Then result addresses contain
| country |
| Liechtenstein |
And results contain
| class | type |
| amenity | ^(pub)\|(bar)\|(restaurant) |
#176
Scenario: Structured search restricts rank
When sending json search query "" with address
| city |
| Vaduz |
Then result addresses contain
| town |
| Vaduz |
#3651
Scenario: Structured search with surrounding extra characters
When sending xml search query "" with address
| street | city | postalcode |
| "19 Am schrägen Weg" | "Vaduz" | "9491" |
Then result addresses contain
| house_number | road |
| 19 | Am Schrägen Weg |

View File

@@ -1,17 +0,0 @@
@UNKNOWNDB
Feature: Status queries against unknown database
Testing status query
Scenario: Failed status as text
When sending text status query
Then a HTTP 500 is returned
And the page contents equals "ERROR: Database connection failed"
Scenario: Failed status as json
When sending json status query
Then a HTTP 200 is returned
And the result is valid json
And results contain
| status | message |
| 700 | Database connection failed |
And result has not attributes data_updated

View File

@@ -1,17 +0,0 @@
@SQLITE
@APIDB
Feature: Status queries
Testing status query
Scenario: Status as text
When sending status query
Then a HTTP 200 is returned
And the page contents equals "OK"
Scenario: Status as json
When sending json status query
Then the result is valid json
And results contain
| status | message |
| 0 | OK |
And result has attributes data_updated

225
test/bdd/conftest.py Normal file
View File

@@ -0,0 +1,225 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Fixtures for BDD test steps
"""
import sys
import json
from pathlib import Path
import pytest
from pytest_bdd.parsers import re as step_parse
from pytest_bdd import when, then
from utils.api_runner import APIRunner
from utils.api_result import APIResult
from utils.checks import ResultAttr, COMPARATOR_TERMS
# always test against the source
SRC_DIR = (Path(__file__) / '..' / '..' / '..').resolve()
sys.path.insert(0, str(SRC_DIR / 'src'))
def _strlist(inp):
return [s.strip() for s in inp.split(',')]
def _pretty_json(inp):
return json.dumps(inp, indent=2)
def pytest_addoption(parser, pluginmanager):
parser.addoption('--nominatim-purge', dest='NOMINATIM_PURGE', action='store_true',
help='Force recreation of test databases from scratch.')
parser.addoption('--nominatim-keep-db', dest='NOMINATIM_KEEP_DB', action='store_true',
help='Do not drop the database after tests are finished.')
parser.addoption('--nominatim-api-engine', dest='NOMINATIM_API_ENGINE',
default='falcon',
help='Chose the API engine to use when sending requests.')
parser.addoption('--nominatim-tokenizer', dest='NOMINATIM_TOKENIZER',
metavar='TOKENIZER',
help='Use the specified tokenizer for importing data into '
'a Nominatim database.')
parser.addini('nominatim_test_db', default='test_nominatim',
help='Name of the database used for running a single test.')
parser.addini('nominatim_api_test_db', default='test_api_nominatim',
help='Name of the database for storing API test data.')
parser.addini('nominatim_template_db', default='test_template_nominatim',
help='Name of database used as a template for test databases.')
@pytest.fixture
def datatable():
""" Default fixture for datatables, so that their presence can be optional.
"""
return None
@when(step_parse(r'reverse geocoding (?P<lat>[\d.-]*),(?P<lon>[\d.-]*)'),
target_fixture='nominatim_result')
def reverse_geocode_via_api(test_config_env, pytestconfig, datatable, lat, lon):
runner = APIRunner(test_config_env, pytestconfig.option.NOMINATIM_API_ENGINE)
api_response = runner.run_step('reverse',
{'lat': float(lat), 'lon': float(lon)},
datatable, 'jsonv2', {})
assert api_response.status == 200
assert api_response.headers['content-type'] == 'application/json; charset=utf-8'
result = APIResult('json', 'reverse', api_response.body)
assert result.is_simple()
return result
@when(step_parse(r'geocoding(?: "(?P<query>.*)")?'),
target_fixture='nominatim_result')
def forward_geocode_via_api(test_config_env, pytestconfig, datatable, query):
runner = APIRunner(test_config_env, pytestconfig.option.NOMINATIM_API_ENGINE)
params = {'addressdetails': '1'}
if query:
params['q'] = query
api_response = runner.run_step('search', params, datatable, 'jsonv2', {})
assert api_response.status == 200
assert api_response.headers['content-type'] == 'application/json; charset=utf-8'
result = APIResult('json', 'search', api_response.body)
assert not result.is_simple()
return result
@then(step_parse(r'(?P<op>[a-z ]+) (?P<num>\d+) results? (?:are|is) returned'),
converters={'num': int})
def check_number_of_results(nominatim_result, op, num):
assert not nominatim_result.is_simple()
assert COMPARATOR_TERMS[op](num, len(nominatim_result))
@then(step_parse('the result metadata contains'))
def check_metadata_for_fields(nominatim_result, datatable):
if datatable[0] == ['param', 'value']:
pairs = datatable[1:]
else:
pairs = zip(datatable[0], datatable[1])
for k, v in pairs:
assert ResultAttr(nominatim_result.meta, k) == v
@then(step_parse('the result metadata has no attributes (?P<attributes>.*)'),
converters={'attributes': _strlist})
def check_metadata_for_field_presence(nominatim_result, attributes):
assert all(a not in nominatim_result.meta for a in attributes), \
f"Unexpectedly have one of the attributes '{attributes}' in\n" \
f"{_pretty_json(nominatim_result.meta)}"
@then(step_parse(r'the result contains(?: in field (?P<field>\S+))?'))
def check_result_for_fields(nominatim_result, datatable, field):
assert nominatim_result.is_simple()
if datatable[0] == ['param', 'value']:
pairs = datatable[1:]
else:
pairs = zip(datatable[0], datatable[1])
prefix = field + '+' if field else ''
for k, v in pairs:
assert ResultAttr(nominatim_result.result, prefix + k) == v
@then(step_parse('the result has attributes (?P<attributes>.*)'),
converters={'attributes': _strlist})
def check_result_for_field_presence(nominatim_result, attributes):
assert nominatim_result.is_simple()
assert all(a in nominatim_result.result for a in attributes)
@then(step_parse('the result has no attributes (?P<attributes>.*)'),
converters={'attributes': _strlist})
def check_result_for_field_absence(nominatim_result, attributes):
assert nominatim_result.is_simple()
assert all(a not in nominatim_result.result for a in attributes)
@then(step_parse('the result set contains(?P<exact> exactly)?'))
def check_result_list_match(nominatim_result, datatable, exact):
assert not nominatim_result.is_simple()
result_set = set(range(len(nominatim_result.result)))
for row in datatable[1:]:
for idx in result_set:
for key, value in zip(datatable[0], row):
if ResultAttr(nominatim_result.result[idx], key) != value:
break
else:
# found a match
result_set.remove(idx)
break
else:
assert False, f"Missing data row {row}. Full response:\n{nominatim_result}"
if exact:
assert not [nominatim_result.result[i] for i in result_set]
@then(step_parse('all results have attributes (?P<attributes>.*)'),
converters={'attributes': _strlist})
def check_all_results_for_field_presence(nominatim_result, attributes):
assert not nominatim_result.is_simple()
for res in nominatim_result.result:
assert all(a in res for a in attributes), \
f"Missing one of the attributes '{attributes}' in\n{_pretty_json(res)}"
@then(step_parse('all results have no attributes (?P<attributes>.*)'),
converters={'attributes': _strlist})
def check_all_result_for_field_absence(nominatim_result, attributes):
assert not nominatim_result.is_simple()
for res in nominatim_result.result:
assert all(a not in res for a in attributes), \
f"Unexpectedly have one of the attributes '{attributes}' in\n{_pretty_json(res)}"
@then(step_parse(r'all results contain(?: in field (?P<field>\S+))?'))
def check_all_results_contain(nominatim_result, datatable, field):
assert not nominatim_result.is_simple()
if datatable[0] == ['param', 'value']:
pairs = datatable[1:]
else:
pairs = zip(datatable[0], datatable[1])
prefix = field + '+' if field else ''
for k, v in pairs:
for r in nominatim_result.result:
assert ResultAttr(r, prefix + k) == v
@then(step_parse(r'result (?P<num>\d+) contains(?: in field (?P<field>\S+))?'),
converters={'num': int})
def check_specific_result_for_fields(nominatim_result, datatable, num, field):
assert not nominatim_result.is_simple()
assert len(nominatim_result) >= num + 1
if datatable[0] == ['param', 'value']:
pairs = datatable[1:]
else:
pairs = zip(datatable[0], datatable[1])
prefix = field + '+' if field else ''
for k, v in pairs:
assert ResultAttr(nominatim_result.result[num], prefix + k) == v

View File

@@ -0,0 +1,83 @@
Feature: Localization of search results
Scenario: default language
When sending v1/details
| osmtype | osmid |
| R | 1155955 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| localname |
| Liechtenstein |
Scenario: accept-language first
When sending v1/details
| osmtype | osmid | accept-language |
| R | 1155955 | zh,de |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| localname |
| |
Scenario: accept-language missing
When sending v1/details
| osmtype | osmid | accept-language |
| R | 1155955 | xx,fr,en,de |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| localname |
| Liechtenstein |
Scenario: http accept language header first
Given the HTTP header
| accept-language |
| fo;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending v1/details
| osmtype | osmid |
| R | 1155955 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| localname |
| Liktinstein |
Scenario: http accept language header and accept-language
Given the HTTP header
| accept-language |
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending v1/details
| osmtype | osmid | accept-language |
| R | 1155955 | fo,en |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| localname |
| Liktinstein |
Scenario: http accept language header fallback
Given the HTTP header
| accept-language |
| fo-ca,en-ca;q=0.5 |
When sending v1/details
| osmtype | osmid |
| R | 1155955 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| localname |
| Liktinstein |
Scenario: http accept language header fallback (upper case)
Given the HTTP header
| accept-language |
| fo-FR;q=0.8,en-ca;q=0.5 |
When sending v1/details
| osmtype | osmid |
| R | 1155955 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| localname |
| Liktinstein |

View File

@@ -0,0 +1,99 @@
Feature: Object details
Testing different parameter options for details API.
Scenario: Basic details
When sending v1/details
| osmtype | osmid |
| W | 297699560 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes geometry
And the result has no attributes keywords,address,linked_places,parentof
And the result contains
| geometry+type |
| Point |
Scenario: Basic details with pretty printing
When sending v1/details
| osmtype | osmid | pretty |
| W | 297699560 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes geometry
And the result has no attributes keywords,address,linked_places,parentof
Scenario: Details with addressdetails
When sending v1/details
| osmtype | osmid | addressdetails |
| W | 297699560 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes address
Scenario: Details with linkedplaces
When sending v1/details
| osmtype | osmid | linkedplaces |
| R | 123924 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes linked_places
Scenario: Details with hierarchy
When sending v1/details
| osmtype | osmid | hierarchy |
| W | 297699560 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes hierarchy
Scenario: Details with grouped hierarchy
When sending v1/details
| osmtype | osmid | hierarchy | group_hierarchy |
| W | 297699560 | 1 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes hierarchy
Scenario Outline: Details with keywords
When sending v1/details
| osmtype | osmid | keywords |
| <type> | <id> | 1 |
Then a HTTP 200 is returned
Then the result is valid json
And the result has attributes keywords
Examples:
| type | id |
| W | 297699560 |
| W | 243055645 |
| W | 243055716 |
| W | 43327921 |
# ticket #1343
Scenario: Details of a country with keywords
When sending v1/details
| osmtype | osmid | keywords |
| R | 1155955 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes keywords
Scenario Outline: Details with full geometry
When sending v1/details
| osmtype | osmid | polygon_geojson |
| <type> | <id> | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes geometry
And the result contains
| geometry+type |
| <geometry> |
Examples:
| type | id | geometry |
| W | 297699560 | LineString |
| W | 243055645 | Polygon |
| W | 243055716 | Polygon |
| W | 43327921 | LineString |

View File

@@ -0,0 +1,99 @@
Feature: Object details
Check details page for correctness
Scenario Outline: Details request with OSM id
When sending v1/details
| osmtype | osmid |
| <type> | <id> |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| osm_type | osm_id |
| <type> | <id> |
Examples:
| type | id |
| N | 5484325405 |
| W | 43327921 |
| R | 123924 |
Scenario Outline: Details request with different class types for the same OSM id
When sending v1/details
| osmtype | osmid | class |
| N | 300209696 | <class> |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| osm_type | osm_id | category |
| N | 300209696 | <class> |
Examples:
| class |
| tourism |
| mountain_pass |
Scenario: Details request without osmtype
When sending v1/details
| osmid |
| <id> |
Then a HTTP 400 is returned
And the result is valid json
Scenario: Details request with unknown OSM id
When sending v1/details
| osmtype | osmid |
| R | 1 |
Then a HTTP 404 is returned
And the result is valid json
Scenario: Details request with unknown class
When sending v1/details
| osmtype | osmid | class |
| N | 300209696 | highway |
Then a HTTP 404 is returned
And the result is valid json
Scenario: Details for interpolation way return the interpolation
When sending v1/details
| osmtype | osmid |
| W | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| category | type | osm_type | osm_id | admin_level |
| place | houses | W | 1 | 15 |
@skip
Scenario: Details for interpolation way return the interpolation
When sending details query for 112871
Then the result is valid json
And the result contains
| category | type | admin_level |
| place | houses | 15 |
And result has not attributes osm_type,osm_id
@skip
Scenario: Details for postcode
When sending details query for 112820
Then the result is valid json
And the result contains
| category | type | admin_level |
| place | postcode | 15 |
And result has not attributes osm_type,osm_id
Scenario Outline: Details debug output returns no errors
When sending v1/details
| osmtype | osmid | debug |
| <type> | <id> | 1 |
Then a HTTP 200 is returned
And the result is valid html
Examples:
| type | id |
| N | 5484325405 |
| W | 43327921 |
| R | 123924 |

View File

@@ -0,0 +1,71 @@
Feature: Tests for finding places by osm_type and osm_id
Simple tests for response format.
Scenario Outline: Address lookup for existing object
When sending v1/lookup with format <format>
| osm_ids |
| N5484325405,W43327921,,R123924,X99,N0 |
Then a HTTP 200 is returned
And the result is valid <outformat>
And exactly 3 results are returned
Examples:
| format | outformat |
| xml | xml |
| json | json |
| jsonv2 | json |
| geojson | geojson |
| geocodejson | geocodejson |
Scenario: Address lookup for non-existing or invalid object
When sending v1/lookup
| osm_ids |
| X99,,N0,nN158845944,ABC,,W9 |
Then a HTTP 200 is returned
And the result is valid xml
And exactly 0 results are returned
Scenario Outline: Boundingbox is returned
When sending v1/lookup with format <format>
| osm_ids |
| N5484325405,W43327921 |
Then the result is valid <outformat>
And the result set contains exactly
| object | boundingbox!in_box |
| N5484325405 | 47.135,47.14,9.52,9.525 |
| W43327921 | 47.07,47.08,9.50,9.52 |
Examples:
| format | outformat |
| xml | xml |
| json | json |
| jsonv2 | json |
| geojson | geojson |
Scenario: Linked places return information from the linkee
When sending v1/lookup with format geocodejson
| osm_ids |
| N1932181216 |
Then the result is valid geocodejson
And exactly 1 result is returned
And all results contain
| name |
| Vaduz |
Scenario Outline: Force error by providing too many ids
When sending v1/lookup with format <format>
| osm_ids |
| N1,N2,N3,N4,N5,N6,N7,N8,N9,N10,N11,N12,N13,N14,N15,N16,N17,N18,N19,N20,N21,N22,N23,N24,N25,N26,N27,N28,N29,N30,N31,N32,N33,N34,N35,N36,N37,N38,N39,N40,N41,N42,N43,N44,N45,N46,N47,N48,N49,N50,N51 |
Then a HTTP 400 is returned
And the result is valid <outformat>
And the result contains
| error+code | error+message |
| 400 | Too many object IDs. |
Examples:
| format | outformat |
| xml | xml |
| json | json |
| jsonv2 | json |
| geojson | json |
| geocodejson | json |

View File

@@ -0,0 +1,56 @@
Feature: Geometries for reverse geocoding
Tests for returning geometries with reverse
Scenario: Reverse - polygons are returned fully by default
When sending v1/reverse
| lat | lon | polygon_text |
| 47.13803 | 9.52264 | 1 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| geotext!fm |
| POLYGON\(\(9.5225302 47.138066, ?9.5225348 47.1379282, ?9.5226142 47.1379294, ?9.5226143 47.1379257, ?9.522615 47.137917, ?9.5226225 47.1379098, ?9.5226334 47.1379052, ?9.5226461 47.1379037, ?9.5226588 47.1379056, ?9.5226693 47.1379107, ?9.5226762 47.1379181, ?9.5226762 47.1379268, ?9.5226761 47.1379308, ?9.5227366 47.1379317, ?9.5227352 47.1379753, ?9.5227608 47.1379757, ?9.5227595 47.1380148, ?9.5227355 47.1380145, ?9.5227337 47.1380692, ?9.5225302 47.138066\)\) |
Scenario: Reverse - polygons can be slightly simplified
When sending v1/reverse
| lat | lon | polygon_text | polygon_threshold |
| 47.13803 | 9.52264 | 1 | 0.00001 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| geotext!fm |
| POLYGON\(\(9.5225302 47.138066, ?9.5225348 47.1379282, ?9.5226142 47.1379294, ?9.5226225 47.1379098, ?9.5226588 47.1379056, ?9.5226761 47.1379308, ?9.5227366 47.1379317, ?9.5227352 47.1379753, ?9.5227608 47.1379757, ?9.5227595 47.1380148, ?9.5227355 47.1380145, ?9.5227337 47.1380692, ?9.5225302 47.138066\)\) |
Scenario: Reverse - polygons can be much simplified
When sending v1/reverse
| lat | lon | polygon_text | polygon_threshold |
| 47.13803 | 9.52264 | 1 | 0.9 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| geotext!fm |
| POLYGON\(\([0-9. ]+, ?[0-9. ]+, ?[0-9. ]+, ?[0-9. ]+(, ?[0-9. ]+)?\)\) |
Scenario: Reverse - for polygons return the centroid as center point
When sending v1/reverse
| lat | lon |
| 47.13836 | 9.52304 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| lon | lat |
| 9.5227108 | 47.1381805 |
Scenario: Reverse - for streets return the closest point as center point
When sending v1/reverse
| lat | lon |
| 47.13368 | 9.52942 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| lon | lat |
| 9.5294315 | 47.1336817 |

View File

@@ -0,0 +1,47 @@
Feature: Localization of reverse search results
Scenario: Reverse - default language
When sending v1/reverse with format jsonv2
| lat | lon |
| 47.14 | 9.55 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| address+country |
| Liechtenstein |
Scenario: Reverse - accept-language parameter
When sending v1/reverse with format jsonv2
| lat | lon | accept-language |
| 47.14 | 9.55 | ja,en |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| address+country |
| |
Scenario: Reverse - HTTP accept language header
Given the HTTP header
| accept-language |
| fo-ca,fo;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending v1/reverse with format jsonv2
| lat | lon |
| 47.14 | 9.55 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| address+country |
| Liktinstein |
Scenario: Reverse - accept-language parameter and HTTP header
Given the HTTP header
| accept-language |
| fo-ca,fo;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending v1/reverse with format jsonv2
| lat | lon | accept-language |
| 47.14 | 9.55 | en |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| address+country |
| Liechtenstein |

View File

@@ -1,24 +1,20 @@
@SQLITE
@APIDB
Feature: Layer parameter in reverse geocoding
Testing correct function of layer selection while reverse geocoding
Scenario: POIs are selected by default
When sending v1/reverse at 47.14077,9.52414
Then results contain
When reverse geocoding 47.14077,9.52414
Then the result contains
| category | type |
| tourism | viewpoint |
Scenario Outline: Same address level POI with different layers
When sending v1/reverse at 47.14077,9.52414
When reverse geocoding 47.14077,9.52414
| layer |
| <layer> |
Then results contain
Then the result contains
| category |
| <category> |
Examples:
| layer | category |
| address | highway |
@@ -28,12 +24,11 @@ Feature: Layer parameter in reverse geocoding
| address,natural | highway |
| natural,poi | tourism |
Scenario Outline: POIs are not selected without housenumber for address layer
When sending v1/reverse at 47.13816,9.52168
When reverse geocoding 47.13816,9.52168
| layer |
| <layer> |
Then results contain
Then the result contains
| category | type |
| <category> | <type> |
@@ -42,21 +37,19 @@ Feature: Layer parameter in reverse geocoding
| address,poi | highway | bus_stop |
| address | amenity | parking |
Scenario: Between natural and low-zoom address prefer natural
When sending v1/reverse at 47.13636,9.52094
When reverse geocoding 47.13636,9.52094
| layer | zoom |
| natural,address | 15 |
Then results contain
Then the result contains
| category |
| waterway |
Scenario Outline: Search for mountain peaks begins at level 12
When sending v1/reverse at 47.08293,9.57109
When reverse geocoding 47.08293,9.57109
| layer | zoom |
| natural | <zoom> |
Then results contain
Then the result contains
| category | type |
| <category> | <type> |
@@ -65,12 +58,11 @@ Feature: Layer parameter in reverse geocoding
| 12 | natural | peak |
| 13 | waterway | river |
Scenario Outline: Reverse search with manmade layers
When sending v1/reverse at 32.46904,-86.44439
When reverse geocoding 32.46904,-86.44439
| layer |
| <layer> |
Then results contain
Then the result contains
| category | type |
| <category> | <type> |

View File

@@ -0,0 +1,80 @@
Feature: Reverse geocoding
Testing the reverse function
Scenario: Reverse - Unknown countries fall back to default country grid
When reverse geocoding 45.174,-103.072
Then the result contains
| category | type | display_name |
| place | country | United States |
Scenario: Reverse - No TIGER house number for zoom < 18
When reverse geocoding 32.4752389363,-86.4810198619
| zoom |
| 17 |
Then the result contains
| osm_type | category |
| way | highway |
And the result contains in field address
| road | postcode | country_code |
| Upper Kingston Road | 36067 | us |
Scenario: Reverse - Address with non-numerical house number
When reverse geocoding 47.107465,9.52838521614
Then the result contains in field address
| house_number | road |
| 39A/B | Dorfstrasse |
Scenario: Reverse - Address with numerical house number
When reverse geocoding 47.168440329479594,9.511551699184338
Then the result contains in field address
| house_number | road |
| 6 | Schmedgässle |
Scenario Outline: Reverse - Zoom levels below 5 result in country
When reverse geocoding 47.16,9.51
| zoom |
| <zoom> |
Then the result contains
| display_name |
| Liechtenstein |
Examples:
| zoom |
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
Scenario: Reverse - When on a street, the closest interpolation is shown
When reverse geocoding 47.118457166193245,9.570678289621355
| zoom |
| 18 |
Then the result contains
| display_name |
| 1021, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
# github 2214
Scenario: Reverse - Interpolations do not override house numbers when they are closer
When reverse geocoding 47.11778,9.57255
| zoom |
| 18 |
Then the result contains
| display_name |
| 5, Grosssteg, Steg, Triesenberg, Oberland, 9497, Liechtenstein |
Scenario: Reverse - Interpolations do not override house numbers when they are closer (2)
When reverse geocoding 47.11834,9.57167
| zoom |
| 18 |
Then the result contains
| display_name |
| 3, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
Scenario: Reverse - When on a street with zoom 18, the closest housenumber is returned
When reverse geocoding 47.11755503977281,9.572722250405036
| zoom |
| 18 |
Then the result contains in field address
| house_number |
| 7 |

View File

@@ -0,0 +1,143 @@
Feature: Geocodejson for Reverse API
Testing correctness of geocodejson output (API version v1).
Scenario Outline: Reverse geocodejson - Simple with no results
When sending v1/reverse with format geocodejson
| lat | lon |
| <lat> | <lon> |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| error |
| Unable to geocode |
Examples:
| lat | lon |
| 0.0 | 0.0 |
| 91.3 | 0.4 |
| -700 | 0.4 |
| 0.2 | 324.44 |
| 0.2 | -180.4 |
Scenario Outline: Reverse geocodejson - Simple OSM result
When sending v1/reverse with format geocodejson
| lat | lon | addressdetails |
| 47.066 | 9.504 | <has_address> |
Then a HTTP 200 is returned
And the result is valid geocodejson with 1 result
And the result metadata contains
| version | licence | attribution!fm |
| 0.1.0 | ODbL | Data © OpenStreetMap contributors, ODbL 1.0. https?://osm.org/copyright |
And all results have <attributes> country,postcode,county,city,district,street,housenumber,admin
And all results contain
| param | value |
| osm_type | node |
| osm_id | 6522627624 |
| osm_key | shop |
| osm_value | bakery |
| type | house |
| name | Dorfbäckerei Herrmann |
| label | Dorfbäckerei Herrmann, 29, Gnetsch, Mäls, Balzers, Oberland, 9496, Liechtenstein |
| geojson+type | Point |
| geojson+coordinates | [9.5036065, 47.0660892] |
Examples:
| has_address | attributes |
| 1 | attributes |
| 0 | no attributes |
Scenario: Reverse geocodejson - City housenumber-level address with street
When sending v1/reverse with format geocodejson
| lat | lon |
| 47.1068011 | 9.52810091 |
Then a HTTP 200 is returned
And the result is valid geocodejson with 1 result
And all results contain
| housenumber | street | postcode | city | country |
| 8 | Im Winkel | 9495 | Triesen | Liechtenstein |
And all results contain
| admin+level6 | admin+level8 |
| Oberland | Triesen |
Scenario: Reverse geocodejson - Town street-level address with street
When sending v1/reverse with format geocodejson
| lat | lon | zoom |
| 47.066 | 9.504 | 16 |
Then a HTTP 200 is returned
And the result is valid geocodejson with 1 result
And all results contain
| name | city | postcode | country |
| Gnetsch | Balzers | 9496 | Liechtenstein |
Scenario: Reverse geocodejson - Poi street-level address with footway
When sending v1/reverse with format geocodejson
| lat | lon |
| 47.06515 | 9.50083 |
Then a HTTP 200 is returned
And the result is valid geocodejson with 1 result
And all results contain
| street | city | postcode | country |
| Burgweg | Balzers | 9496 | Liechtenstein |
Scenario: Reverse geocodejson - City address with suburb
When sending v1/reverse with format geocodejson
| lat | lon |
| 47.146861 | 9.511771 |
Then a HTTP 200 is returned
And the result is valid geocodejson with 1 result
And all results contain
| housenumber | street | district | city | postcode | country |
| 5 | Lochgass | Ebenholz | Vaduz | 9490 | Liechtenstein |
Scenario: Reverse geocodejson - Tiger address
When sending v1/reverse with format geocodejson
| lat | lon |
| 32.4752389363 | -86.4810198619 |
Then a HTTP 200 is returned
And the result is valid geocodejson with 1 result
And all results contain
| osm_type | osm_id | osm_key | osm_value | type |
| way | 396009653 | place | house | house |
And all results contain
| housenumber | street | city | county | postcode | country |
| 707 | Upper Kingston Road | Prattville | Autauga County | 36067 | United States |
Scenario: Reverse geocodejson - Interpolation address
When sending v1/reverse with format geocodejson
| lat | lon |
| 47.118533 | 9.57056562 |
Then a HTTP 200 is returned
And the result is valid geocodejson with 1 result
And all results contain
| osm_type | osm_id | osm_key | osm_value | type |
| way | 1 | place | house | house |
And all results contain
| label |
| 1019, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
And all results have no attributes name
Scenario: Reverse geocodejson - Line geometry output is supported
When sending v1/reverse with format geocodejson
| lat | lon | polygon_geojson |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid geocodejson with 1 result
And all results contain
| geojson+type |
| LineString |
Scenario Outline: Reverse geocodejson - Only geojson polygons are supported
When sending v1/reverse with format geocodejson
| lat | lon | <param> |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid geocodejson with 1 result
And all results contain
| geojson+type |
| Point |
Examples:
| param |
| polygon_text |
| polygon_svg |
| polygon_kml |

View File

@@ -0,0 +1,102 @@
Feature: Geojson for Reverse API
Testing correctness of geojson output (API version v1).
Scenario Outline: Reverse geojson - Simple with no results
When sending v1/reverse with format geojson
| lat | lon |
| <lat> | <lon> |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| error |
| Unable to geocode |
Examples:
| lat | lon |
| 0.0 | 0.0 |
| 91.3 | 0.4 |
| -700 | 0.4 |
| 0.2 | 324.44 |
| 0.2 | -180.4 |
Scenario Outline: Reverse geojson - Simple OSM result
When sending v1/reverse with format geojson
| lat | lon | addressdetails |
| 47.066 | 9.504 | <has_address> |
Then a HTTP 200 is returned
And the result is valid geojson with 1 result
And the result metadata contains
| licence!fm |
| Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright |
And all results have attributes place_id, importance
And all results have <attributes> address
And all results contain
| param | value |
| osm_type | node |
| osm_id | 6522627624 |
| place_rank | 30 |
| category | shop |
| type | bakery |
| addresstype | shop |
| name | Dorfbäckerei Herrmann |
| display_name | Dorfbäckerei Herrmann, 29, Gnetsch, Mäls, Balzers, Oberland, 9496, Liechtenstein |
| boundingbox | [47.0660392, 47.0661392, 9.5035565, 9.5036565] |
| geojson+type | Point |
| geojson+coordinates | [9.5036065, 47.0660892] |
Examples:
| has_address | attributes |
| 1 | attributes |
| 0 | no attributes |
Scenario: Reverse geojson - Tiger address
When sending v1/reverse with format geojson
| lat | lon |
| 32.4752389363 | -86.4810198619 |
Then a HTTP 200 is returned
And the result is valid geojson with 1 result
And all results contain
| osm_type | osm_id | category | type | addresstype | place_rank |
| way | 396009653 | place | house | place | 30 |
Scenario: Reverse geojson - Interpolation address
When sending v1/reverse with format geojson
| lat | lon |
| 47.118533 | 9.57056562 |
Then a HTTP 200 is returned
And the result is valid geojson with 1 result
And all results contain
| osm_type | osm_id | place_rank | category | type | addresstype |
| way | 1 | 30 | place | house | place |
And all results contain
| boundingbox!in_box |
| 47.118494, 47.118596, 9.570495, 9.570597 |
And all results contain
| display_name |
| 1019, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
Scenario: Reverse geojson - Line geometry output is supported
When sending v1/reverse with format geojson
| lat | lon | polygon_geojson |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid geojson with 1 result
And all results contain
| geojson+type |
| LineString |
Scenario Outline: Reverse geojson - Only geojson polygons are supported
When sending v1/reverse with format geojson
| lat | lon | <param> |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid geojson with 1 result
And all results contain
| geojson+type |
| Point |
Examples:
| param |
| polygon_text |
| polygon_svg |
| polygon_kml |

View File

@@ -0,0 +1,175 @@
Feature: Json output for Reverse API
Testing correctness of json and jsonv2 output (API version v1).
Scenario Outline: Reverse json - Simple with no results
When sending v1/reverse with format json
| lat | lon |
| <lat> | <lon> |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| error |
| Unable to geocode |
When sending v1/reverse with format jsonv2
| lat | lon |
| <lat> | <lon> |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| error |
| Unable to geocode |
Examples:
| lat | lon |
| 0.0 | 0.0 |
| 91.3 | 0.4 |
| -700 | 0.4 |
| 0.2 | 324.44 |
| 0.2 | -180.4 |
Scenario Outline: Reverse json - OSM result with and without addresses
When sending v1/reverse with format json
| lat | lon | addressdetails |
| 47.066 | 9.504 | <has_address> |
Then a HTTP 200 is returned
And the result is valid json
And the result has <attributes> address
When sending v1/reverse with format jsonv2
| lat | lon | addressdetails |
| 47.066 | 9.504 | <has_address> |
Then a HTTP 200 is returned
And the result is valid json
And the result has <attributes> address
Examples:
| has_address | attributes |
| 1 | attributes |
| 0 | no attributes |
Scenario Outline: Reverse json - Simple OSM result
When sending v1/reverse with format <format>
| lat | lon |
| 47.066 | 9.504 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes place_id
And the result contains
| licence!fm |
| Data © OpenStreetMap contributors, ODbL 1.0. https?://osm.org/copyright |
And the result contains
| osm_type | osm_id |
| node | 6522627624 |
And the result contains
| lon | lat | boundingbox!in_box |
| 9.5036065 | 47.0660892 | 47.0660391, 47.0661393, 9.5035564, 9.5036566 |
And the result contains
| display_name |
| Dorfbäckerei Herrmann, 29, Gnetsch, Mäls, Balzers, Oberland, 9496, Liechtenstein |
And the result has no attributes namedetails,extratags
Examples:
| format |
| json |
| jsonv2 |
Scenario: Reverse json - Extra attributes of jsonv2 result
When sending v1/reverse with format jsonv2
| lat | lon |
| 47.066 | 9.504 |
Then a HTTP 200 is returned
And the result is valid json
And the result has attributes importance
And the result contains
| category | type | name | place_rank | addresstype |
| shop | bakery | Dorfbäckerei Herrmann | 30 | shop |
Scenario: Reverse json - Tiger address
When sending v1/reverse with format jsonv2
| lat | lon |
| 32.4752389363 | -86.4810198619 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| osm_type | osm_id | category | type | addresstype |
| way | 396009653 | place | house | place |
Scenario Outline: Reverse json - Interpolation address
When sending v1/reverse with format <format>
| lat | lon |
| 47.118533 | 9.57056562 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| osm_type | osm_id |
| way | 1 |
And the result contains
| lon | lat | boundingbox!in_box |
| 9.5705468 | 47.1185454 | 47.118494, 47.118596, 9.570495, 9.570597 |
And the result contains
| display_name |
| 1019, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
Examples:
| format |
| json |
| jsonv2 |
Scenario Outline: Reverse json - Output of geojson
When sending v1/reverse with format <format>
| lat | lon | polygon_geojson |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| geojson+type | geojson+coordinates |
| LineString | [[9.5039353, 47.0657546], [9.5040437, 47.0657781], [9.5040808, 47.065787], [9.5054298, 47.0661407]] |
Examples:
| format |
| json |
| jsonv2 |
Scenario Outline: Reverse json - Output of WKT
When sending v1/reverse with format <format>
| lat | lon | polygon_text |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| geotext!fm |
| LINESTRING\(9.5039353 47.0657546, ?9.5040437 47.0657781, ?9.5040808 47.065787, ?9.5054298 47.0661407\) |
Examples:
| format |
| json |
| jsonv2 |
Scenario Outline: Reverse json - Output of SVG
When sending v1/reverse with format <format>
| lat | lon | polygon_svg |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| svg |
| M 9.5039353 -47.0657546 L 9.5040437 -47.0657781 9.5040808 -47.065787 9.5054298 -47.0661407 |
Examples:
| format |
| json |
| jsonv2 |
Scenario Outline: Reverse json - Output of KML
When sending v1/reverse with format <format>
| lat | lon | polygon_kml |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| geokml!fm |
| <LineString><coordinates>9.5039\d*,47.0657\d* 9.5040\d*,47.0657\d* 9.5040\d*,47.065\d* 9.5054\d*,47.0661\d*</coordinates></LineString> |
Examples:
| format |
| json |
| jsonv2 |

View File

@@ -0,0 +1,169 @@
Feature: v1/reverse Parameter Tests
Tests for parameter inputs for the v1 reverse endpoint.
This file contains mostly bad parameter input. Valid parameters
are tested in the format tests.
Scenario: Bad format
When sending v1/reverse
| lat | lon | format |
| 47.14122383 | 9.52169581334 | sdf |
Then a HTTP 400 is returned
Scenario: Missing lon parameter
When sending v1/reverse
| lat |
| 52.52 |
Then a HTTP 400 is returned
Scenario: Missing lat parameter
When sending v1/reverse
| lon |
| 52.52 |
Then a HTTP 400 is returned
Scenario Outline: Bad format for lat or lon
When sending v1/reverse
| lat | lon |
| <lat> | <lon> |
Then a HTTP 400 is returned
Examples:
| lat | lon |
| 48.9660 | 8,4482 |
| 48,9660 | 8.4482 |
| 48,9660 | 8,4482 |
| 48.966.0 | 8.4482 |
| 48.966 | 8.448.2 |
| Nan | 8.448 |
| 48.966 | Nan |
| Inf | 5.6 |
| 5.6 | -Inf |
| <script></script> | 3.4 |
| 3.4 | <script></script> |
| -45.3 | ; |
| gkjd | 50 |
Scenario: Non-numerical zoom levels return an error
When sending v1/reverse
| lat | lon | zoom |
| 47.14122383 | 9.52169581334 | adfe |
Then a HTTP 400 is returned
Scenario Outline: Truthy values for boolean parameters
When sending v1/reverse
| lat | lon | addressdetails |
| 47.14122383 | 9.52169581334 | <value> |
Then a HTTP 200 is returned
And the result is valid xml
And the result has attributes address
When sending v1/reverse
| lat | lon | extratags |
| 47.14122383 | 9.52169581334 | <value> |
Then a HTTP 200 is returned
And the result is valid xml
And the result has attributes extratags
When sending v1/reverse
| lat | lon | namedetails |
| 47.14122383 | 9.52169581334 | <value> |
Then a HTTP 200 is returned
And the result is valid xml
And the result has attributes namedetails
Examples:
| value |
| yes |
| no |
| -1 |
| 100 |
| false |
| 00 |
Scenario: Only one geometry can be requested
When sending v1/reverse
| lat | lon | polygon_text | polygon_svg |
| 47.14122383 | 9.52169581334 | 1 | 1 |
Then a HTTP 400 is returned
Scenario Outline: Illegal jsonp are not allowed
When sending v1/reverse with format json
| lat | lon | json_callback |
| 47.14122383 | 9.52169581334 | <data> |
Then a HTTP 400 is returned
Examples:
| data |
| 1asd |
| bar(foo) |
| XXX['bad'] |
| foo; evil |
Scenario Outline: Reverse debug mode produces valid HTML
When sending v1/reverse
| lat | lon | debug |
| <lat> | <lon> | 1 |
Then a HTTP 200 is returned
And the result is valid html
Examples:
| lat | lon |
| 0.0 | 0.0 |
| 47.06645 | 9.56601 |
| 47.14081 | 9.52267 |
Scenario Outline: Full address display for city housenumber-level address with street
When sending v1/reverse with format <format>
| lat | lon |
| 47.1068011 | 9.52810091 |
Then a HTTP 200 is returned
And the result is valid <outformat>
And the result contains in field address
| param | value |
| house_number | 8 |
| road | Im Winkel |
| neighbourhood | Oberdorf |
| village | Triesen |
| ISO3166-2-lvl8 | LI-09 |
| county | Oberland |
| postcode | 9495 |
| country | Liechtenstein |
| country_code | li |
Examples:
| format | outformat |
| json | json |
| jsonv2 | json |
| xml | xml |
Scenario Outline: Results with name details
When sending v1/reverse with format <format>
| lat | lon | zoom | namedetails |
| 47.14052 | 9.52202 | 14 | 1 |
Then a HTTP 200 is returned
And the result is valid <outformat>
And the result contains in field namedetails
| name |
| Ebenholz |
Examples:
| format | outformat |
| json | json |
| jsonv2 | json |
| xml | xml |
Scenario Outline: Results with extratags
When sending v1/reverse with format <format>
| lat | lon | zoom | extratags |
| 47.14052 | 9.52202 | 14 | 1 |
Then a HTTP 200 is returned
And the result is valid <outformat>
And the result contains in field extratags
| wikidata |
| Q4529531 |
Examples:
| format | outformat |
| json | json |
| jsonv2 | json |
| xml | xml |

View File

@@ -0,0 +1,116 @@
Feature: XML output for Reverse API
Testing correctness of xml output (API version v1).
Scenario Outline: Reverse XML - Simple reverse-geocoding with no results
When sending v1/reverse
| lat | lon |
| <lat> | <lon> |
Then a HTTP 200 is returned
And the result is valid xml
And the result has no attributes osm_type, address, extratags
And the result contains
| error |
| Unable to geocode |
Examples:
| lat | lon |
| 0.0 | 0.0 |
| 91.3 | 0.4 |
| -700 | 0.4 |
| 0.2 | 324.44 |
| 0.2 | -180.4 |
Scenario Outline: Reverse XML - OSM result with and without addresses
When sending v1/reverse with format xml
| lat | lon | addressdetails |
| 47.066 | 9.504 | <has_address> |
Then a HTTP 200 is returned
And the result is valid xml
And the result has attributes place_id
And the result has <attributes> address
And the result contains
| osm_type | osm_id | place_rank | address_rank |
| node | 6522627624 | 30 | 30 |
And the result contains
| lon | lat | boundingbox |
| 9.5036065 | 47.0660892 | 47.0660392,47.0661392,9.5035565,9.5036565 |
And the result contains
| ref | display_name |
| Dorfbäckerei Herrmann | Dorfbäckerei Herrmann, 29, Gnetsch, Mäls, Balzers, Oberland, 9496, Liechtenstein |
Examples:
| has_address | attributes |
| 1 | attributes |
| 0 | no attributes |
Scenario: Reverse XML - Tiger address
When sending v1/reverse with format xml
| lat | lon |
| 32.4752389363 | -86.4810198619 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| osm_type | osm_id | place_rank | address_rank |
| way | 396009653 | 30 | 30 |
And the result contains
| lon | lat | boundingbox |
| -86.4808553 | 32.4753580 | 32.4753080,32.4754080,-86.4809053,-86.4808053 |
And the result contains
| display_name |
| 707, Upper Kingston Road, Upper Kingston, Prattville, Autauga County, 36067, United States |
Scenario: Reverse XML - Interpolation address
When sending v1/reverse with format xml
| lat | lon |
| 47.118533 | 9.57056562 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| osm_type | osm_id | place_rank | address_rank |
| way | 1 | 30 | 30 |
And the result contains
| lon | lat | boundingbox |
| 9.5705468 | 47.1185454 | 47.1184954,47.1185954,9.5704968,9.5705968 |
And the result contains
| display_name |
| 1019, Grosssteg, Sücka, Triesenberg, Oberland, 9497, Liechtenstein |
Scenario: Reverse XML - Output of geojson
When sending v1/reverse with format xml
| lat | lon | polygon_geojson |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| geojson |
| {"type":"LineString","coordinates":[[9.5039353,47.0657546],[9.5040437,47.0657781],[9.5040808,47.065787],[9.5054298,47.0661407]]} |
Scenario: Reverse XML - Output of WKT
When sending v1/reverse with format xml
| lat | lon | polygon_text |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| geotext!fm |
| LINESTRING\(9.5039353 47.0657546, ?9.5040437 47.0657781, ?9.5040808 47.065787, ?9.5054298 47.0661407\) |
Scenario: Reverse XML - Output of SVG
When sending v1/reverse with format xml
| lat | lon | polygon_svg |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| geosvg |
| M 9.5039353 -47.0657546 L 9.5040437 -47.0657781 9.5040808 -47.065787 9.5054298 -47.0661407 |
Scenario: Reverse XML - Output of KML
When sending v1/reverse with format xml
| lat | lon | polygon_kml |
| 47.06597 | 9.50467 | 1 |
Then a HTTP 200 is returned
And the result is valid xml
And the result contains
| geokml!fm |
| <geokml><LineString><coordinates>9.5039\d*,47.0657\d* 9.5040\d*,47.0657\d* 9.5040\d*,47.065\d* 9.5054\d*,47.0661\d*</coordinates></LineString></geokml> |

View File

@@ -0,0 +1,83 @@
Feature: Localization of search results
Scenario: Search - default language
When sending v1/search
| q |
| Liechtenstein |
Then a HTTP 200 is returned
And the result is valid json
And result 0 contains
| display_name |
| Liechtenstein |
Scenario: Search - accept-language first
When sending v1/search
| q | accept-language |
| Liechtenstein | zh,de |
Then a HTTP 200 is returned
And the result is valid json
And result 0 contains
| display_name |
| |
Scenario: Search - accept-language missing
When sending v1/search
| q | accept-language |
| Liechtenstein | xx,fr,en,de |
Then a HTTP 200 is returned
And the result is valid json
And result 0 contains
| display_name |
| Liechtenstein |
Scenario: Search - http accept language header first
Given the HTTP header
| accept-language |
| fo;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending v1/search
| q |
| Liechtenstein |
Then a HTTP 200 is returned
And the result is valid json
And result 0 contains
| display_name |
| Liktinstein |
Scenario: Search - http accept language header and accept-language
Given the HTTP header
| accept-language |
| fr-ca,fr;q=0.8,en-ca;q=0.5,en;q=0.3 |
When sending v1/search
| q | accept-language |
| Liechtenstein | fo,en |
Then a HTTP 200 is returned
And the result is valid json
And result 0 contains
| display_name |
| Liktinstein |
Scenario: Search - http accept language header fallback
Given the HTTP header
| accept-language |
| fo-ca,en-ca;q=0.5 |
When sending v1/search
| q |
| Liechtenstein |
Then a HTTP 200 is returned
And the result is valid json
And result 0 contains
| display_name |
| Liktinstein |
Scenario: Search - http accept language header fallback (upper case)
Given the HTTP header
| accept-language |
| fo-FR;q=0.8,en-ca;q=0.5 |
When sending v1/search
| q |
| Liechtenstein |
Then a HTTP 200 is returned
And the result is valid json
And result 0 contains
| display_name |
| Liktinstein |

View File

@@ -0,0 +1,361 @@
Feature: Search queries
Testing different queries and parameters
Scenario: Simple XML search
When sending v1/search with format xml
| q |
| Schaan |
Then a HTTP 200 is returned
And the result is valid xml
And all results have attributes place_id,osm_type,osm_id
And all results have attributes place_rank,boundingbox
And all results have attributes lat,lon,display_name
And all results have attributes class,type,importance
And all results have no attributes address
And all results contain
| boundingbox!in_box |
| 46.5,47.5,9,10 |
Scenario Outline: Simple JSON search
When sending v1/search with format <format>
| q |
| Vaduz |
Then a HTTP 200 is returned
And the result is valid json
And all results have attributes place_id,licence,<cname>,type
And all results have attributes osm_type,osm_id,boundingbox
And all results have attributes lat,lon,display_name,importance
And all results have no attributes address
And all results contain
| boundingbox!in_box |
| 46.5,47.5,9,10 |
Examples:
| format | cname |
| json | class |
| jsonv2 | category |
Scenario: Unknown formats returns a user error
When sending v1/search with format x45
| q |
| Vaduz |
Then a HTTP 400 is returned
Scenario Outline: Search with addressdetails
When sending v1/search with format <format>
| q | addressdetails |
| Triesen | 1 |
Then a HTTP 200 is returned
And the result is valid <outformat>
And result 0 contains in field address
| param | value |
| village | Triesen |
| county | Oberland |
| postcode | 9495 |
| country | Liechtenstein |
| country_code | li |
| ISO3166-2-lvl8 | LI-09 |
Examples:
| format | outformat |
| json | json |
| jsonv2 | json |
| geojson | geojson |
| xml | xml |
Scenario: Coordinate search with addressdetails
When geocoding "47.12400621,9.6047552"
| accept-language |
| en |
Then all results contain
| display_name |
| Guschg, Valorschstrasse, Balzers, Oberland, 9497, Liechtenstein |
Scenario: Address details with unknown class types
When geocoding "Kloster St. Elisabeth"
Then result 0 contains
| category | type | address+amenity |
| amenity | monastery | Kloster St. Elisabeth |
Scenario: Disabling deduplication
When geocoding "Malbunstr, Schaan"
Then exactly 1 result is returned
When geocoding "Malbunstr, Schaan"
| dedupe |
| 0 |
Then exactly 4 results are returned
Scenario: Search with bounded viewbox in right area
When geocoding "post"
| bounded | viewbox |
| 1 | 9,47,10,48 |
Then result 0 contains
| address+town |
| Vaduz |
When geocoding "post"
| bounded | viewbox |
| 1 | 9.49712,47.17122,9.52605,47.16242 |
Then result 0 contains
| address+town |
| Schaan |
Scenario: Country search with bounded viewbox remain in the area
When geocoding
| bounded | viewbox | country |
| 1 | 9.49712,47.17122,9.52605,47.16242 | de |
Then exactly 0 results are returned
Scenario: Search with bounded viewboxlbrt in right area
When geocoding "bar"
| bounded | viewboxlbrt |
| 1 | 9.49712,47.16242,9.52605,47.17122 |
Then all results contain
| address+town |
| Schaan |
Scenario: No POI search with unbounded viewbox
When geocoding "restaurant"
| viewbox |
| 9.93027,53.61634,10.10073,53.54500 |
Then all results contain
| display_name!fm |
| .*[Rr]estaurant.* |
Scenario: bounded search remains within viewbox, even with no results
When geocoding "[restaurant]"
| bounded | viewbox |
| 1 | 43.5403125,-5.6563282,43.54285,-5.662003 |
Then exactly 0 results are returned
Scenario: bounded search remains within viewbox with results
When geocoding "restaurant"
| bounded | viewbox |
| 1 | 9.49712,47.17122,9.52605,47.16242 |
Then all results contain
| boundingbox!in_box |
| 47.16242,47.17122,9.49712,9.52605 |
Scenario: Prefer results within viewbox
When geocoding "Gässle"
| accept-language | viewbox |
| en | 9.52413,47.10759,9.53140,47.10539 |
Then result 0 contains
| address+village |
| Triesen |
When geocoding "Gässle"
| accept-language | viewbox |
| en | 9.45949,47.08421,9.54094,47.05466 |
Then result 0 contains
| address+town |
| Balzers |
Scenario: viewboxes cannot be points
When sending v1/search
| q | viewbox |
| foo | 1.01,34.6,1.01,34.6 |
Then a HTTP 400 is returned
Scenario Outline: viewbox must have four coordinate numbers
When sending v1/search
| q | viewbox |
| foo | <viewbox> |
Then a HTTP 400 is returned
Examples:
| viewbox |
| 34 |
| 0.003,-84.4 |
| 5.2,4.5542,12.4 |
| 23.1,-6,0.11,44.2,9.1 |
Scenario Outline: viewboxlbrt must have four coordinate numbers
When sending v1/search
| q | viewboxlbrt |
| foo | <viewbox> |
Then a HTTP 400 is returned
Examples:
| viewbox |
| 34 |
| 0.003,-84.4 |
| 5.2,4.5542,12.4 |
| 23.1,-6,0.11,44.2,9.1 |
Scenario: Overly large limit number for search results
When geocoding "restaurant"
| limit |
| 1000 |
Then exactly 35 results are returned
Scenario: Limit number of non-duplicated search results
When geocoding "landstr"
| dedupe |
| 0 |
Then exactly 10 results are returned
When geocoding "landstr"
| limit | dedupe |
| 4 | 0 |
Then exactly 4 results are returned
Scenario: Limit parameter must be a number
When sending v1/search
| q | limit |
| Blue Laguna | ); |
Then a HTTP 400 is returned
Scenario: Restrict to feature type country
When geocoding "fürstentum"
| featureType |
| country |
Then all results contain
| place_rank |
| 4 |
Scenario: Restrict to feature type state
When geocoding "Wangerberg"
Then more than 0 results are returned
When geocoding "Wangerberg"
| featureType |
| state |
Then exactly 0 results are returned
Scenario: Restrict to feature type city
When geocoding "vaduz"
| featureType |
| state |
Then exactly 0 results are returned
When geocoding "vaduz"
| featureType |
| city |
Then more than 0 results are returned
Then all results contain
| place_rank |
| 16 |
Scenario: Restrict to feature type settlement
When geocoding "Malbun"
Then result 1 contains
| category |
| landuse |
When geocoding "Malbun"
| featureType |
| settlement |
Then all results contain
| category | type |
| place | village |
Scenario Outline: Search with polygon threshold (json)
When sending v1/search with format json
| q | polygon_geojson | polygon_threshold |
| Triesenberg | 1 | <th> |
Then a HTTP 200 is returned
And the result is valid json
And more than 0 results are returned
And all results have attributes geojson
Examples:
| th |
| -1 |
| 0.0 |
| 0.5 |
| 999 |
Scenario Outline: Search with polygon threshold (xml)
When sending v1/search with format xml
| q | polygon_geojson | polygon_threshold |
| Triesenberg | 1 | <th> |
Then a HTTP 200 is returned
And the result is valid xml
And more than 0 results are returned
And all results have attributes geojson
Examples:
| th |
| -1 |
| 0.0 |
| 0.5 |
| 999 |
Scenario Outline: Search with invalid polygon threshold (xml)
When sending v1/search with format xml
| q | polygon_geojson | polygon_threshold |
| Triesenberg | 1 | <th> |
Then a HTTP 400 is returned
Examples:
| th |
| x |
| ;; |
| 1m |
Scenario Outline: Search with extratags
When sending v1/search with format <format>
| q | extratags |
| Landstr | 1 |
Then a HTTP 200 is returned
And the result is valid <outformat>
And more than 0 results are returned
Then all results have attributes extratags
Examples:
| format | outformat |
| xml | xml |
| json | json |
| jsonv2 | json |
| geojson | geojson |
Scenario Outline: Search with namedetails
When sending v1/search with format <format>
| q | namedetails |
| Landstr | 1 |
Then a HTTP 200 is returned
And the result is valid <outformat>
And more than 0 results are returned
Then all results have attributes namedetails
Examples:
| format | outformat |
| xml | xml |
| json | json |
| jsonv2 | json |
| geojson | geojson |
Scenario Outline: Search result with contains formatted geometry
When sending v1/search with format <format>
| q | <param> |
| Triesenberg | 1 |
Then a HTTP 200 is returned
And the result is valid <outformat>
And more than 0 results are returned
And all results have attributes <response_attribute>
Examples:
| format | outformat | param | response_attribute |
| xml | xml | polygon_text | geotext |
| json | json | polygon_text | geotext |
| jsonv2 | json | polygon_text | geotext |
| xml | xml | polygon_svg | geosvg |
| json | json | polygon_svg | svg |
| jsonv2 | json | polygon_svg | svg |
| xml | xml | polygon_kml | geokml |
| json | json | polygon_kml | geokml |
| jsonv2 | json | polygon_kml | geokml |
| xml | xml | polygon_geojson | geojson |
| json | json | polygon_geojson | geojson |
| jsonv2 | json | polygon_geojson | geojson |
| geojson | geojson | polygon_geojson | geojson |
Scenario Outline: Search result in geojson format contains no non-geojson geometry
When sending v1/search with format geojson
| q | <param> |
| Triesenberg | 1 |
Then a HTTP 200 is returned
And the result is valid geojson
And more than 0 results are returned
And all results have no attributes <response_attribute>
Examples:
| param | response_attribute |
| polygon_text | geotext |
| polygon_svg | svg |
| polygon_kml | geokml |

View File

@@ -1,51 +1,51 @@
@SQLITE
@APIDB
Feature: Searches with postcodes
Various searches involving postcodes
Scenario: US 5+4 ZIP codes are shortened to 5 ZIP codes if not found
When sending json search query "36067-1111, us" with address
Then result addresses contain
When geocoding "36067-1111, us"
Then all results contain in field address
| postcode |
| 36067 |
And results contain
And all results contain
| type |
| postcode |
Scenario: Postcode search with address
When sending json search query "9486, mauren"
Then at least 1 result is returned
When geocoding "9486, mauren"
Then result 0 contains
| type |
| postcode |
Scenario: Postcode search with country
When sending json search query "9486, li" with address
Then result addresses contain
When geocoding "9486, li"
Then all results contain in field address
| country_code |
| li |
Scenario: Postcode search with country code restriction
When sending json search query "9490" with address
When geocoding "9490"
| countrycodes |
| li |
Then result addresses contain
Then all results contain in field address
| country_code |
| li |
Scenario: Postcode search with bounded viewbox restriction
When sending json search query "9486" with address
When geocoding "9486"
| bounded | viewbox |
| 1 | 9.55,47.20,9.58,47.22 |
Then result addresses contain
Then all results contain in field address
| postcode |
| 9486 |
When sending json search query "9486" with address
When geocoding "9486"
| bounded | viewbox |
| 1 | 5.00,20.00,6.00,21.00 |
Then exactly 0 results are returned
Then exactly 0 result is returned
Scenario: Postcode search with structured query
When sending json search query "" with address
When geocoding ""
| postalcode | country |
| 9490 | li |
Then result addresses contain
Then all results contain in field address
| country_code | postcode |
| li | 9490 |

View File

@@ -0,0 +1,212 @@
Feature: Search queries
Generic search result correctness
Scenario: Search for natural object
When geocoding "Samina"
| accept-language |
| en |
Then result 0 contains
| category | type | display_name |
| waterway | river | Samina, Austria |
Scenario: House number search for non-street address
When geocoding "6 Silum, Liechtenstein"
| accept-language |
| en |
Then result 0 contains in field address
| param | value |
| house_number | 6 |
| village | Silum |
| town | Triesenberg |
| county | Oberland |
| postcode | 9497 |
| country | Liechtenstein |
| country_code | li |
| ISO3166-2-lvl8 | LI-10 |
Scenario: Search for house number interpolation
When geocoding "Grosssteg 1023, Triesenberg"
| accept-language |
| de |
Then result 0 contains in field address
| param | value |
| house_number | 1023 |
| road | Grosssteg |
| village | Sücka |
| postcode | 9497 |
| town | Triesenberg |
| country | Liechtenstein |
| country_code | li |
Scenario: With missing housenumber search falls back to road
When geocoding "Bündaweg 555"
Then result 0 contains in field address
| param | value |
| road | Bündaweg |
| village | Silum |
| postcode | 9497 |
| county | Oberland |
| town | Triesenberg |
| country | Liechtenstein |
| country_code | li |
| ISO3166-2-lvl8 | LI-10 |
And all results have no attributes address+house_number
Scenario Outline: Housenumber 0 can be found
When sending v1/search with format <format>
| q | addressdetails |
| Gnalpstrasse 0 | 1 |
Then a HTTP 200 is returned
And the result is valid <outformat>
And all results contain
| display_name!fm | address+house_number |
| 0,.* | 0 |
Examples:
| format | outformat |
| xml | xml |
| json | json |
| jsonv2 | json |
| geojson | geojson |
Scenario: TIGER house number
When geocoding "697 Upper Kingston Road"
Then all results contain
| osm_type | display_name!fm | address+house_number |
| way | 697,.* | 697 |
Scenario: Search with class-type feature
When geocoding "bars in ebenholz"
Then all results contain
| place_rank |
| 30 |
Scenario: Search with specific amenity
When geocoding "[restaurant] Vaduz"
Then all results contain
| category | type | address+country |
| amenity | restaurant | Liechtenstein |
Scenario: Search with specific amenity also work in country
When geocoding "restaurants in liechtenstein"
Then all results contain
| category | type | address+country |
| amenity | restaurant | Liechtenstein |
Scenario: Search with key-value amenity
When geocoding "[club=scout] Vaduz"
Then all results contain
| category | type |
| club | scout |
Scenario: POI search near given coordinate
When geocoding "restaurant near 47.16712,9.51100"
Then all results contain
| category | type |
| amenity | restaurant |
Scenario: Arbitrary key/value search near given coordinate
When geocoding "[leisure=firepit] 47.150° N 9.5340493° E"
Then all results contain
| category | type |
| leisure | firepit |
Scenario: POI search in a bounded viewbox
When geocoding "restaurants"
| viewbox | bounded |
| 9.50830,47.15253,9.52043,47.14866 | 1 |
Then all results contain
| category | type |
| amenity | restaurant |
Scenario Outline: Key/value search near given coordinate can be restricted to country
When geocoding "[natural=peak] 47.06512,9.53965"
| countrycodes |
| <cc> |
Then all results contain
| address+country_code |
| <cc> |
Examples:
| cc |
| li |
| ch |
Scenario: Name search near given coordinate
When geocoding "sporry"
Then result 0 contains
| address+town |
| Vaduz |
When geocoding "sporry, 47.10791,9.52676"
Then result 0 contains
| address+village |
| Triesen |
Scenario: Name search near given coordinate without result
When geocoding "sporry, N 47 15 7 W 9 61 26"
Then exactly 0 results are returned
Scenario: Arbitrary key/value search near a road
When geocoding "[amenity=drinking_water] Wissfläckaweg"
Then all results contain
| category | type |
| amenity | drinking_water |
Scenario: Ignore other country codes in structured search with country
When geocoding
| countrycodes | country |
| li | de |
Then exactly 0 results are returned
Scenario: Ignore country searches when query is restricted to countries
When geocoding "fr"
Then all results contain
| name |
| France |
When geocoding "fr"
| countrycodes |
| li |
Then exactly 0 results are returned
Scenario: Country searches only return results for the given country
When geocoding "Ans Trail"
| countrycodes |
| li |
Then all results contain
| address+country_code |
| li |
# https://trac.openstreetmap.org/ticket/5094
Scenario: housenumbers are ordered by complete match first
When geocoding "Austrasse 11, Vaduz"
Then result 0 contains
| address+house_number |
| 11 |
Scenario Outline: Coordinate searches with white spaces
When geocoding "<data>"
Then the result set contains exactly
| category |
| water |
Examples:
| data |
| sporry weiher, N 47.10791° E 9.52676° |
| sporry weiher, N 47.10791° E 9.52676° |
| sporry weiher , N 47.10791° E 9.52676° |
| sporry weiher, N 47.10791° E 9.52676° |
| sporry weiher , N 47.10791° E 9.52676° |
Scenario: Searches with white spaces
When geocoding "52 Bodastr , Triesenberg"
Then all results contain
| category | type |
| highway | residential |
# github #1949
Scenario: Addressdetails always return the place type
When geocoding "Vaduz"
Then result 0 contains
| address+town |
| Vaduz |

View File

@@ -0,0 +1,166 @@
Feature: Simple Tests
Simple tests for internal server errors and response format.
Scenario Outline: Garbage Searches
When sending v1/search
| q |
| <query> |
Then a HTTP 200 is returned
And the result is valid json
And exactly 0 results are returned
Examples:
| query |
| New York, New York |
| 12, Main Street, Houston |
| München |
| |
| hotels in sdfewf |
| xywxkrf |
| gh; foo() |
| %#$@*&l;der#$! |
| 234.23.14.5 |
| aussenstelle universitat lichtenstein wachterhaus aussenstelle universitat lichtenstein wachterhaus aussenstelle universitat lichtenstein wachterhaus aussenstelle universitat lichtenstein wachterhaus |
Scenario: Empty XML search
When sending v1/search with format xml
| q |
| xnznxvcx |
Then a HTTP 200 is returned
And the result is valid xml
Then the result metadata contains
| param | value |
| querystring | xnznxvcx |
| more_url!fm | .*q=xnznxvcx.*format=xml |
Scenario: Empty XML search with special XML characters
When sending v1/search with format xml
| q |
| xfdghn&zxn"xvbyx<vxx>cssdex |
Then a HTTP 200 is returned
And the result is valid xml
Then the result metadata contains
| param | value |
| querystring | xfdghn&zxn"xvbyx<vxx>cssdex |
| more_url!fm | .*q=xfdghn%26zxn%22xvbyx%3Cvxx%3Ecssdex.*format=xml |
Scenario: Empty XML search with viewbox
When sending v1/search with format xml
| q | viewbox |
| xnznxvcx | 12,33,77,45.13 |
Then a HTTP 200 is returned
And the result is valid xml
And the result metadata contains
| param | value |
| querystring | xnznxvcx |
| viewbox | 12,33,77,45.13 |
Scenario: Empty XML search with viewboxlbrt
When sending v1/search with format xml
| q | viewboxlbrt |
| xnznxvcx | 12,34.13,77,45 |
Then a HTTP 200 is returned
And the result is valid xml
And the result metadata contains
| param | value |
| querystring | xnznxvcx |
| viewbox | 12,34.13,77,45 |
Scenario: Empty XML search with viewboxlbrt and viewbox
When sending v1/search with format xml
| q | viewbox | viewboxblrt |
| pub | 12,33,77,45.13 | 1,2,3,4 |
Then a HTTP 200 is returned
And the result is valid xml
And the result metadata contains
| param | value |
| querystring | pub |
| viewbox | 12,33,77,45.13 |
Scenario: Empty XML search with excluded place ids
When sending v1/search with format xml
| q | exclude_place_ids |
| jghrleoxsbwjer | 123,76,342565 |
Then a HTTP 200 is returned
And the result is valid xml
And the result metadata contains
| param | value |
| exclude_place_ids | 123,76,342565 |
Scenario: Empty XML search with bad excluded place ids
When sending v1/search with format xml
| q | exclude_place_ids |
| jghrleoxsbwjer | , |
Then a HTTP 200 is returned
And the result is valid xml
And the result metadata has no attributes exclude_place_ids
Scenario Outline: Wrapping of illegal jsonp search requests
When sending v1/search with format json
| q | json_callback |
| Tokyo | <data> |
Then a HTTP 400 is returned
And the result is valid json
And the result contains
| error+code | error+message |
| 400 | Invalid json_callback value |
Examples:
| data |
| 1asd |
| bar(foo) |
| XXX['bad'] |
| foo; evil |
| 234 |
Scenario: Ignore jsonp parameter for anything but json
When sending v1/search with format xml
| q | json_callback |
| Tokyo | 234 |
Then a HTTP 200 is returned
Then the result is valid xml
Scenario Outline: Empty search for json like
When sending v1/search with format <format>
| q |
| YHlERzzx |
Then a HTTP 200 is returned
And the result is valid <outformat>
And exactly 0 results are returned
Examples:
| format | outformat |
| json | json |
| jsonv2 | json |
| geojson | geojson |
| geocodejson | geocodejson |
Scenario: Search for non-existing coordinates
When geocoding "-21.0,-33.0"
Then exactly 0 results are returned
Scenario: Country code selection is retained in more URL (#596)
When sending v1/search with format xml
| q | countrycodes |
| Vaduz | pl,1,,invalid,undefined,%3Cb%3E,bo,, |
Then a HTTP 200 is returned
And the result is valid xml
And the result metadata contains
| more_url!fm |
| .*&countrycodes=pl%2Cbo&.* |
Scenario Outline: Search debug output does not return errors
When sending v1/search
| q | debug |
| <query> | 1 |
Then a HTTP 200 is returned
And the result is valid html
Examples:
| query |
| Liechtenstein |
| Triesen |
| Pfarrkirche |
| Landstr 27 Steinort, Triesenberg, 9495 |
| 9497 |
| restaurant in triesen |

View File

@@ -0,0 +1,72 @@
Feature: Structured search queries
Testing correctness of results with
structured queries
Scenario: Structured search for country only
When geocoding
| country |
| Liechtenstein |
Then all results contain in field address
| country_code | country |
| li | Liechtenstein |
Scenario: Structured search for postcode only
When geocoding
| postalcode |
| 9495 |
Then all results contain
| type!fm | address+postcode |
| ^post(al_)?code | 9495 |
Scenario: Structured search for street, postcode and country
When sending v1/search with format xml
| street | postalcode | country |
| Old Palace Road | GU2 7UP | United Kingdom |
Then a HTTP 200 is returned
And the result is valid xml
And the result metadata contains
| querystring |
| Old Palace Road, GU2 7UP, United Kingdom |
Scenario: Structured search for street with housenumber, city and postcode
When geocoding
| street | city | postalcode |
| 19 Am schrägen Weg | Vaduz | 9490 |
Then all results contain in field address
| house_number | road |
| 19 | Am Schrägen Weg |
Scenario: Structured search for street with housenumber, city and bad postcode
When geocoding
| street | city | postalcode |
| 19 Am schrägen Weg | Vaduz | 9491 |
Then all results contain in field address
| house_number | road |
| 19 | Am Schrägen Weg |
Scenario: Structured search for amenity, city
When geocoding
| city | amenity |
| Vaduz | bar |
Then all results contain
| address+country | category | type!fm |
| Liechtenstein | amenity | (pub)\|(bar)\|(restaurant) |
#176
Scenario: Structured search restricts rank
When geocoding
| city |
| Steg |
Then all results contain
| addresstype |
| village |
#3651
Scenario: Structured search with surrounding extra characters
When geocoding
| street | city | postalcode |
| "19 Am schrägen Weg" | "Vaduz" | "9491" |
Then all results contain in field address
| house_number | road |
| 19 | Am Schrägen Weg |

View File

@@ -0,0 +1,42 @@
Feature: Search API geocodejson output
Testing correctness of geocodejson output.
Scenario: Search geocodejson - City housenumber-level address with street
When sending v1/search with format geocodejson
| q | addressdetails |
| Im Winkel 8, Triesen | 1 |
Then a HTTP 200 is returned
And the result is valid geocodejson
And all results contain
| housenumber | street | postcode | city | country |
| 8 | Im Winkel | 9495 | Triesen | Liechtenstein |
Scenario: Search geocodejson - Town street-level address with street
When sending v1/search with format geocodejson
| q | addressdetails |
| Gnetsch, Balzers | 1 |
Then a HTTP 200 is returned
And the result is valid geocodejson
And all results contain
| name | city | postcode | country |
| Gnetsch | Balzers | 9496 | Liechtenstein |
Scenario: Search geocodejson - Town street-level address with footway
When sending v1/search with format geocodejson
| q | addressdetails |
| burg gutenberg 6000 jahre geschichte | 1 |
Then a HTTP 200 is returned
And the result is valid geocodejson
And all results contain
| street | city | postcode | country |
| Burgweg | Balzers | 9496 | Liechtenstein |
Scenario: Search geocodejson - City address with suburb
When sending v1/search with format geocodejson
| q | addressdetails |
| Lochgass 5, Ebenholz, Vaduz | 1 |
Then a HTTP 200 is returned
And the result is valid geocodejson
And all results contain
| housenumber | street | district | city | postcode | country |
| 5 | Lochgass | Ebenholz | Vaduz | 9490 | Liechtenstein |

View File

@@ -0,0 +1,19 @@
Feature: Status queries against unknown database
Testing status query
Background:
Given an unknown database
Scenario: Failed status as text
When sending v1/status
Then a HTTP 500 is returned
And the page content equals "ERROR: Database connection failed"
Scenario: Failed status as json
When sending v1/status with format json
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| status!:d | message |
| 700 | Database connection failed |
And the result has no attributes data_updated

View File

@@ -0,0 +1,15 @@
Feature: Status queries
Testing status query
Scenario: Status as text
When sending v1/status
Then a HTTP 200 is returned
And the page content equals "OK"
Scenario: Status as json
When sending v1/status with format json
Then a HTTP 200 is returned
And the result is valid json
And the result contains
| status!:d | message | data_updated!fm |
| 0 | OK | ....-..-..T..:..:...00:00 |

153
test/bdd/test_api.py Normal file
View File

@@ -0,0 +1,153 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Collector for all BDD API tests.
These tests work on a static test database that is the same for all tests.
The source data for the database can be found in the test/testdb directory.
"""
from pathlib import Path
import xml.etree.ElementTree as ET
import pytest
from pytest_bdd.parsers import re as step_parse
from pytest_bdd import scenarios, when, given, then
from nominatim_db import cli
from nominatim_db.config import Configuration
from utils.db import DBManager
from utils.api_runner import APIRunner
from utils.api_result import APIResult
TESTDB_PATH = (Path(__file__) / '..' / '..' / 'testdb').resolve()
CONTENT_TYPES = {
'json': 'application/json; charset=utf-8',
'xml': 'text/xml; charset=utf-8',
'geojson': 'application/json; charset=utf-8',
'geocodejson': 'application/json; charset=utf-8',
'html': 'text/html; charset=utf-8'
}
@pytest.fixture(autouse=True, scope='session')
def session_api_test_db(pytestconfig):
""" Create a Nominatim database from the official API test data.
Will only recreate an existing database if --nominatim-purge
was set.
"""
dbname = pytestconfig.getini('nominatim_api_test_db')
config = Configuration(None).get_os_env()
config['NOMINATIM_DATABASE_DSN'] = f"pgsql:dbname={dbname}"
config['NOMINATIM_LANGUAGES'] = 'en,de,fr,ja'
config['NOMINATIM_USE_US_TIGER_DATA'] = 'yes'
if pytestconfig.option.NOMINATIM_TOKENIZER is not None:
config['NOMINATIM_TOKENIZER'] = pytestconfig.option.NOMINATIM_TOKENIZER
dbm = DBManager(purge=pytestconfig.option.NOMINATIM_PURGE)
if not dbm.check_for_db(dbname):
try:
cli.nominatim(cli_args=['import', '--project-dir', str(TESTDB_PATH),
'--osm-file', str(TESTDB_PATH / 'apidb-test-data.pbf')],
environ=config)
cli.nominatim(cli_args=['add-data', '--project-dir', str(TESTDB_PATH),
'--tiger-data', str(TESTDB_PATH / 'tiger')],
environ=config)
cli.nominatim(cli_args=['freeze', '--project-dir', str(TESTDB_PATH)],
environ=config)
cli.nominatim(cli_args=['special-phrases', '--project-dir', str(TESTDB_PATH),
'--import-from-csv',
str(TESTDB_PATH / 'full_en_phrases_test.csv')],
environ=config)
except: # noqa: E722
dbm.drop_db(dbname)
raise
@pytest.fixture
def test_config_env(pytestconfig):
dbname = pytestconfig.getini('nominatim_api_test_db')
config = Configuration(None).get_os_env()
config['NOMINATIM_DATABASE_DSN'] = f"pgsql:dbname={dbname}"
config['NOMINATIM_LANGUAGES'] = 'en,de,fr,ja'
config['NOMINATIM_USE_US_TIGER_DATA'] = 'yes'
if pytestconfig.option.NOMINATIM_TOKENIZER is not None:
config['NOMINATIM_TOKENIZER'] = pytestconfig.option.NOMINATIM_TOKENIZER
return config
@pytest.fixture
def api_http_request_headers():
return {}
@given('the HTTP header', target_fixture='api_http_request_headers')
def set_additional_http_headers(api_http_request_headers, datatable):
api_http_request_headers.update(zip(datatable[0], datatable[1]))
return api_http_request_headers
@given('an unknown database', target_fixture='test_config_env')
def setup_connection_unknown_database(test_config_env):
test_config_env['NOMINATIM_DATABASE_DSN'] = "pgsql:dbname=gerlkghngergn6732nf"
return test_config_env
@when(step_parse(r'sending v1/(?P<endpoint>\S+)(?: with format (?P<fmt>\S+))?'),
target_fixture='api_response')
def send_api_status(test_config_env, api_http_request_headers, pytestconfig,
datatable, endpoint, fmt):
runner = APIRunner(test_config_env, pytestconfig.option.NOMINATIM_API_ENGINE)
return runner.run_step(endpoint, {}, datatable, fmt, api_http_request_headers)
@then(step_parse(r'a HTTP (?P<status>\d+) is returned'), converters={'status': int})
def check_http_result(api_response, status):
assert api_response.status == status
@then(step_parse('the page content equals "(?P<content>.*)"'))
def check_page_content_exact(api_response, content):
assert api_response.body == content
@then('the result is valid html')
def check_for_html_correctness(api_response):
assert api_response.headers['content-type'] == CONTENT_TYPES['html']
try:
tree = ET.fromstring(api_response.body)
except Exception as ex:
assert False, f"Could not parse page: {ex}\n{api_response.body}"
assert tree.tag == 'html'
body = tree.find('./body')
assert body is not None
assert body.find('.//script') is None
@then(step_parse(r'the result is valid (?P<fmt>\S+)(?: with (?P<num>\d+) results?)?'),
target_fixture='nominatim_result')
def parse_api_json_response(api_response, fmt, num):
assert api_response.headers['content-type'] == CONTENT_TYPES[fmt]
result = APIResult(fmt, api_response.endpoint, api_response.body)
if num:
assert len(result) == int(num)
return result
scenarios('features/api')

View File

View File

@@ -0,0 +1,133 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Wrapper for results from the API
"""
import json
import xml.etree.ElementTree as ET
class APIResult:
def __init__(self, fmt, endpoint, body):
getattr(self, '_parse_' + fmt)(endpoint, body)
def is_simple(self):
return not isinstance(self.result, list)
def __len__(self):
return 1 if self.is_simple() else len(self.result)
def __str__(self):
return json.dumps({'meta': self.meta, 'result': self.result}, indent=2)
def _parse_json(self, _, body):
self.meta = {}
self.result = json.loads(body)
def _parse_xml(self, endpoint, body):
xml_tree = ET.fromstring(body)
self.meta = dict(xml_tree.attrib)
if xml_tree.tag == 'reversegeocode':
self._parse_xml_simple(xml_tree)
elif xml_tree.tag == 'searchresults':
self._parse_xml_multi(xml_tree)
elif xml_tree.tag == 'error':
self.result = {'error': {sub.tag: sub.text for sub in xml_tree}}
def _parse_xml_simple(self, xml):
self.result = {}
for child in xml:
if child.tag == 'result':
assert not self.result, "More than one result in reverse result"
self.result.update(child.attrib)
assert 'display_name' not in self.result
self.result['display_name'] = child.text
elif child.tag == 'addressparts':
assert 'address' not in self.result
self.result['address'] = {sub.tag: sub.text for sub in child}
elif child.tag == 'extratags':
assert 'extratags' not in self.result
self.result['extratags'] = {tag.attrib['key']: tag.attrib['value'] for tag in child}
elif child.tag == 'namedetails':
assert 'namedetails' not in self.result
self.result['namedetails'] = {tag.attrib['desc']: tag.text for tag in child}
elif child.tag == 'geokml':
assert 'geokml' not in self.result
self.result['geokml'] = ET.tostring(child, encoding='unicode')
elif child.tag == 'error':
assert not self.result
self.result['error'] = child.text
else:
assert False, f"Unknown XML tag {child.tag} on page: {self.page}"
def _parse_xml_multi(self, xml):
self.result = []
for child in xml:
assert child.tag == "place"
res = dict(child.attrib)
address = {}
for sub in child:
if sub.tag == 'extratags':
assert 'extratags' not in res
res['extratags'] = {tag.attrib['key']: tag.attrib['value'] for tag in sub}
elif sub.tag == 'namedetails':
assert 'namedetails' not in res
res['namedetails'] = {tag.attrib['desc']: tag.text for tag in sub}
elif sub.tag == 'geokml':
res['geokml'] = ET.tostring(sub, encoding='utf-8')
else:
address[sub.tag] = sub.text
if address:
res['address'] = address
self.result.append(res)
def _parse_geojson(self, _, body):
geojson = json.loads(body)
assert geojson.get('type') == 'FeatureCollection'
assert isinstance(geojson.get('features'), list)
self.meta = {k: v for k, v in geojson.items() if k not in ('type', 'features')}
self.result = []
for obj in geojson['features']:
assert isinstance(obj, dict)
assert obj.get('type') == 'Feature'
assert isinstance(obj.get('properties'), dict)
result = obj['properties']
assert 'geojson' not in result
result['geojson'] = obj['geometry']
if 'bbox' in obj:
assert 'boundingbox' not in result
# bbox is minlon, minlat, maxlon, maxlat
# boundingbox is minlat, maxlat, minlon, maxlon
result['boundingbox'] = [obj['bbox'][1], obj['bbox'][3],
obj['bbox'][0], obj['bbox'][2]]
self.result.append(result)
def _parse_geocodejson(self, endpoint, body):
self._parse_geojson(endpoint, body)
assert set(self.meta.keys()) == {'geocoding'}
assert isinstance(self.meta['geocoding'], dict)
self.meta = self.meta['geocoding']
for r in self.result:
assert set(r.keys()) == {'geocoding', 'geojson'}
inner = r.pop('geocoding')
assert isinstance(inner, dict)
assert 'geojson' not in inner
r.update(inner)

View File

@@ -0,0 +1,70 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Various helper classes for running Nominatim commands.
"""
import asyncio
from collections import namedtuple
APIResponse = namedtuple('APIResponse', ['endpoint', 'status', 'body', 'headers'])
class APIRunner:
""" Execute a call to an API endpoint.
"""
def __init__(self, environ, api_engine):
create_func = getattr(self, f"create_engine_{api_engine}")
self.exec_engine = create_func(environ)
def run(self, endpoint, params, http_headers):
return asyncio.run(self.exec_engine(endpoint, params, http_headers))
def run_step(self, endpoint, base_params, datatable, fmt, http_headers):
if fmt:
base_params['format'] = fmt.strip()
if datatable:
if datatable[0] == ['param', 'value']:
base_params.update(datatable[1:])
else:
base_params.update(zip(datatable[0], datatable[1]))
return self.run(endpoint, base_params, http_headers)
def create_engine_falcon(self, environ):
import nominatim_api.server.falcon.server
import falcon.testing
async def exec_engine_falcon(endpoint, params, http_headers):
app = nominatim_api.server.falcon.server.get_application(None, environ)
async with falcon.testing.ASGIConductor(app) as conductor:
response = await conductor.get("/" + endpoint, params=params,
headers=http_headers)
return APIResponse(endpoint, response.status_code,
response.text, response.headers)
return exec_engine_falcon
def create_engine_starlette(self, environ):
import nominatim_api.server.starlette.server
from asgi_lifespan import LifespanManager
import httpx
async def _request(endpoint, params, http_headers):
app = nominatim_api.server.starlette.server.get_application(None, environ)
async with LifespanManager(app):
async with httpx.AsyncClient(app=app, base_url="http://nominatim.test") as client:
response = await client.get("/" + endpoint, params=params,
headers=http_headers)
return APIResponse(endpoint, response.status_code,
response.text, response.headers)
return _request

109
test/bdd/utils/checks.py Normal file
View File

@@ -0,0 +1,109 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Helper functions to compare expected values.
"""
import json
import re
COMPARATOR_TERMS = {
'exactly': lambda exp, act: exp == act,
'more than': lambda exp, act: act > exp,
'less than': lambda exp, act: act < exp,
}
def _pretty(obj):
return json.dumps(obj, sort_keys=True, indent=2)
def within_box(value, expect):
coord = [float(x) for x in expect.split(',')]
if isinstance(value, str):
value = value.split(',')
value = list(map(float, value))
if len(value) == 2:
return coord[0] <= value[0] <= coord[2] \
and coord[1] <= value[1] <= coord[3]
if len(value) == 4:
return value[0] >= coord[0] and value[1] <= coord[1] \
and value[2] >= coord[2] and value[3] <= coord[3]
raise ValueError("Not a coordinate or bbox.")
COMPARISON_FUNCS = {
None: lambda val, exp: str(val) == exp,
'i': lambda val, exp: str(val).lower() == exp.lower(),
'fm': lambda val, exp: re.fullmatch(exp, val) is not None,
'in_box': within_box
}
OSM_TYPE = {'node': 'n', 'way': 'w', 'relation': 'r'}
class ResultAttr:
""" Returns the given attribute as a string.
The key parameter determines how the value is formatted before
returning. To refer to sub attributes, use '+' to add more keys
(e.g. 'name+ref' will access obj['name']['ref']). A '!' introduces
a formatting suffix. If no suffix is given, the value will be
converted using the str() function.
Available formatters:
!:... - use a formatting expression according to Python Mini Format Spec
!i - make case-insensitive comparison
!fm - consider comparison string a regular expression and match full value
"""
def __init__(self, obj, key):
self.obj = obj
if '!' in key:
self.key, self.fmt = key.rsplit('!', 1)
else:
self.key = key
self.fmt = None
if self.key == 'object':
assert 'osm_id' in obj
assert 'osm_type' in obj
self.subobj = OSM_TYPE[obj['osm_type']] + str(obj['osm_id'])
self.fmt = 'i'
else:
done = ''
self.subobj = self.obj
for sub in self.key.split('+'):
done += f"[{sub}]"
assert sub in self.subobj, \
f"Missing attribute {done}. Full object:\n{_pretty(self.obj)}"
self.subobj = self.subobj[sub]
def __eq__(self, other):
if not isinstance(other, str):
raise NotImplementedError()
# work around bad quoting by pytest-bdd
other = other.replace(r'\\', '\\')
if self.fmt in COMPARISON_FUNCS:
return COMPARISON_FUNCS[self.fmt](self.subobj, other)
if self.fmt.startswith(':'):
return other == f"{{{self.fmt}}}".format(self.subobj)
raise RuntimeError(f"Unknown format string '{self.fmt}'.")
def __repr__(self):
k = self.key.replace('+', '][')
if self.fmt:
k += '!' + self.fmt
return f"result[{k}]({self.subobj})"

44
test/bdd/utils/db.py Normal file
View File

@@ -0,0 +1,44 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Nominatim. (https://nominatim.org)
#
# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Helper functions for managing test databases.
"""
import psycopg
from psycopg import sql as pysql
class DBManager:
def __init__(self, purge=False):
self.purge = purge
def check_for_db(self, dbname):
""" Check if the given DB already exists.
When the purge option is set, then an existing database will
be deleted and the function returns that it does not exist.
"""
if self.purge:
self.drop_db(dbname)
return False
return self.exists_db(dbname)
def drop_db(self, dbname):
""" Drop the given database if it exists.
"""
with psycopg.connect(dbname='postgres') as conn:
conn.autocommit = True
conn.execute(pysql.SQL('DROP DATABASE IF EXISTS')
+ pysql.Identifier(dbname))
def exists_db(self, dbname):
""" Check if a database with the given name exists already.
"""
with psycopg.connect(dbname='postgres') as conn:
cur = conn.execute('select count(*) from pg_database where datname = %s',
(dbname,))
return cur.fetchone()[0] == 1