From 6959577aa479d61e5c1cce6336212c02d577df75 Mon Sep 17 00:00:00 2001 From: Sarah Hoffmann Date: Tue, 18 Mar 2025 11:19:06 +0100 Subject: [PATCH] replace behave BDD API tests with pytest-bdd tests --- Makefile | 3 +- test/bdd/api/details/language.feature | 63 --- test/bdd/api/details/params.feature | 96 ----- test/bdd/api/details/simple.feature | 81 ---- test/bdd/api/errors/formats.feature | 14 - test/bdd/api/lookup/simple.feature | 42 -- test/bdd/api/reverse/geometry.feature | 45 --- test/bdd/api/reverse/language.feature | 37 -- test/bdd/api/reverse/queries.feature | 117 ------ test/bdd/api/reverse/v1_geocodejson.feature | 107 ------ test/bdd/api/reverse/v1_geojson.feature | 73 ---- test/bdd/api/reverse/v1_json.feature | 130 ------- test/bdd/api/reverse/v1_params.feature | 206 ---------- test/bdd/api/reverse/v1_xml.feature | 88 ----- test/bdd/api/search/geocodejson.feature | 28 -- test/bdd/api/search/language.feature | 63 --- test/bdd/api/search/params.feature | 362 ------------------ test/bdd/api/search/queries.feature | 221 ----------- test/bdd/api/search/simple.feature | 208 ---------- test/bdd/api/search/structured.feature | 79 ---- test/bdd/api/status/failures.feature | 17 - test/bdd/api/status/simple.feature | 17 - test/bdd/conftest.py | 225 +++++++++++ .../bdd/features/api/details/language.feature | 83 ++++ test/bdd/features/api/details/params.feature | 99 +++++ test/bdd/features/api/details/simple.feature | 99 +++++ test/bdd/features/api/lookup/simple.feature | 71 ++++ .../bdd/features/api/reverse/geometry.feature | 56 +++ .../bdd/features/api/reverse/language.feature | 47 +++ .../{ => features}/api/reverse/layers.feature | 32 +- test/bdd/features/api/reverse/queries.feature | 80 ++++ .../api/reverse/v1_geocodejson.feature | 143 +++++++ .../features/api/reverse/v1_geojson.feature | 102 +++++ test/bdd/features/api/reverse/v1_json.feature | 175 +++++++++ .../features/api/reverse/v1_params.feature | 169 ++++++++ test/bdd/features/api/reverse/v1_xml.feature | 116 ++++++ test/bdd/features/api/search/language.feature | 83 ++++ test/bdd/features/api/search/params.feature | 361 +++++++++++++++++ .../api/search/postcode.feature | 34 +- test/bdd/features/api/search/queries.feature | 212 ++++++++++ test/bdd/features/api/search/simple.feature | 166 ++++++++ .../features/api/search/structured.feature | 72 ++++ .../api/search/v1_geocodejson.feature | 42 ++ test/bdd/features/api/status/failures.feature | 19 + test/bdd/features/api/status/simple.feature | 15 + test/bdd/test_api.py | 153 ++++++++ test/bdd/utils/__init__.py | 0 test/bdd/utils/api_result.py | 133 +++++++ test/bdd/utils/api_runner.py | 70 ++++ test/bdd/utils/checks.py | 109 ++++++ test/bdd/utils/db.py | 44 +++ 51 files changed, 2975 insertions(+), 2132 deletions(-) delete mode 100644 test/bdd/api/details/language.feature delete mode 100644 test/bdd/api/details/params.feature delete mode 100644 test/bdd/api/details/simple.feature delete mode 100644 test/bdd/api/errors/formats.feature delete mode 100644 test/bdd/api/lookup/simple.feature delete mode 100644 test/bdd/api/reverse/geometry.feature delete mode 100644 test/bdd/api/reverse/language.feature delete mode 100644 test/bdd/api/reverse/queries.feature delete mode 100644 test/bdd/api/reverse/v1_geocodejson.feature delete mode 100644 test/bdd/api/reverse/v1_geojson.feature delete mode 100644 test/bdd/api/reverse/v1_json.feature delete mode 100644 test/bdd/api/reverse/v1_params.feature delete mode 100644 test/bdd/api/reverse/v1_xml.feature delete mode 100644 test/bdd/api/search/geocodejson.feature delete mode 100644 test/bdd/api/search/language.feature delete mode 100644 test/bdd/api/search/params.feature delete mode 100644 test/bdd/api/search/queries.feature delete mode 100644 test/bdd/api/search/simple.feature delete mode 100644 test/bdd/api/search/structured.feature delete mode 100644 test/bdd/api/status/failures.feature delete mode 100644 test/bdd/api/status/simple.feature create mode 100644 test/bdd/conftest.py create mode 100644 test/bdd/features/api/details/language.feature create mode 100644 test/bdd/features/api/details/params.feature create mode 100644 test/bdd/features/api/details/simple.feature create mode 100644 test/bdd/features/api/lookup/simple.feature create mode 100644 test/bdd/features/api/reverse/geometry.feature create mode 100644 test/bdd/features/api/reverse/language.feature rename test/bdd/{ => features}/api/reverse/layers.feature (80%) create mode 100644 test/bdd/features/api/reverse/queries.feature create mode 100644 test/bdd/features/api/reverse/v1_geocodejson.feature create mode 100644 test/bdd/features/api/reverse/v1_geojson.feature create mode 100644 test/bdd/features/api/reverse/v1_json.feature create mode 100644 test/bdd/features/api/reverse/v1_params.feature create mode 100644 test/bdd/features/api/reverse/v1_xml.feature create mode 100644 test/bdd/features/api/search/language.feature create mode 100644 test/bdd/features/api/search/params.feature rename test/bdd/{ => features}/api/search/postcode.feature (58%) create mode 100644 test/bdd/features/api/search/queries.feature create mode 100644 test/bdd/features/api/search/simple.feature create mode 100644 test/bdd/features/api/search/structured.feature create mode 100644 test/bdd/features/api/search/v1_geocodejson.feature create mode 100644 test/bdd/features/api/status/failures.feature create mode 100644 test/bdd/features/api/status/simple.feature create mode 100644 test/bdd/test_api.py create mode 100644 test/bdd/utils/__init__.py create mode 100644 test/bdd/utils/api_result.py create mode 100644 test/bdd/utils/api_runner.py create mode 100644 test/bdd/utils/checks.py create mode 100644 test/bdd/utils/db.py diff --git a/Makefile b/Makefile index f35c9782..864e9385 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/test/bdd/api/details/language.feature b/test/bdd/api/details/language.feature deleted file mode 100644 index 5351ce41..00000000 --- a/test/bdd/api/details/language.feature +++ /dev/null @@ -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 | diff --git a/test/bdd/api/details/params.feature b/test/bdd/api/details/params.feature deleted file mode 100644 index 0fb64171..00000000 --- a/test/bdd/api/details/params.feature +++ /dev/null @@ -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 - | 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 - | polygon_geojson | - | 1 | - Then the result is valid json - And result has attributes geometry - And results contain in field geometry - | type | - | | - - Examples: - | osmid | geometry | - | W297699560 | LineString | - | W243055645 | Polygon | - | W243055716 | Polygon | - | W43327921 | LineString | - - diff --git a/test/bdd/api/details/simple.feature b/test/bdd/api/details/simple.feature deleted file mode 100644 index a3cc95e5..00000000 --- a/test/bdd/api/details/simple.feature +++ /dev/null @@ -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 - Then the result is valid json - And results contain - | osm_type | osm_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: - Then the result is valid json - And results contain - | osm_type | osm_id | category | - | N | 300209696 | | - - Examples: - | class | - | tourism | - | mountain_pass | - - - Scenario Outline: Details via unknown OSM id - When sending details query for - 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 - Then the result is valid html - - Examples: - | feature | - | N5484325405 | - | W1 | - | 112820 | - | 112871 | diff --git a/test/bdd/api/errors/formats.feature b/test/bdd/api/errors/formats.feature deleted file mode 100644 index e279a8fa..00000000 --- a/test/bdd/api/errors/formats.feature +++ /dev/null @@ -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 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 user error is returned - - Examples: - | format | - | xml | - | json | - | geojson | diff --git a/test/bdd/api/lookup/simple.feature b/test/bdd/api/lookup/simple.feature deleted file mode 100644 index 1e5b8ee7..00000000 --- a/test/bdd/api/lookup/simple.feature +++ /dev/null @@ -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 lookup query for N5484325405,W43327921,,R123924,X99,N0 - Then the result is valid - 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 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 | diff --git a/test/bdd/api/reverse/geometry.feature b/test/bdd/api/reverse/geometry.feature deleted file mode 100644 index aac82807..00000000 --- a/test/bdd/api/reverse/geometry.feature +++ /dev/null @@ -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 | diff --git a/test/bdd/api/reverse/language.feature b/test/bdd/api/reverse/language.feature deleted file mode 100644 index 69f84ebc..00000000 --- a/test/bdd/api/reverse/language.feature +++ /dev/null @@ -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 | diff --git a/test/bdd/api/reverse/queries.feature b/test/bdd/api/reverse/queries.feature deleted file mode 100644 index fc28cee3..00000000 --- a/test/bdd/api/reverse/queries.feature +++ /dev/null @@ -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 , - 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 | - | | - 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 | diff --git a/test/bdd/api/reverse/v1_geocodejson.feature b/test/bdd/api/reverse/v1_geocodejson.feature deleted file mode 100644 index 56b85e20..00000000 --- a/test/bdd/api/reverse/v1_geocodejson.feature +++ /dev/null @@ -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 | - | | - Then result has attributes place_id, accuracy - And result has 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 | - | | 1 | - Then results contain in field geojson - | type | - | Point | - - Examples: - | param | - | polygon_text | - | polygon_svg | - | polygon_kml | diff --git a/test/bdd/api/reverse/v1_geojson.feature b/test/bdd/api/reverse/v1_geojson.feature deleted file mode 100644 index e705529d..00000000 --- a/test/bdd/api/reverse/v1_geojson.feature +++ /dev/null @@ -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 | - | | - Then result has attributes place_id, importance, __licence - And result has 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 | - | | 1 | - Then results contain in field geojson - | type | - | Point | - - Examples: - | param | - | polygon_text | - | polygon_svg | - | polygon_kml | diff --git a/test/bdd/api/reverse/v1_json.feature b/test/bdd/api/reverse/v1_json.feature deleted file mode 100644 index 1f629c0f..00000000 --- a/test/bdd/api/reverse/v1_json.feature +++ /dev/null @@ -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 | - | | - Then result has address - When sending v1/reverse at 47.066,9.504 with format jsonv2 - | addressdetails | - | | - Then result has 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 - 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 - 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 - | 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 - | 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 - | 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 - | param | value | - | polygon_kml | 1 | - Then results contain - | geokml | - | ^9.5039\d*,47.0657\d* 9.5040\d*,47.0657\d* 9.5040\d*,47.065\d* 9.5054\d*,47.0661\d* | - - Examples: - | format | - | json | - | jsonv2 | diff --git a/test/bdd/api/reverse/v1_params.feature b/test/bdd/api/reverse/v1_params.feature deleted file mode 100644 index 09a190ed..00000000 --- a/test/bdd/api/reverse/v1_params.feature +++ /dev/null @@ -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 | - | | | - 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 | - | | 3.4 | - | 3.4 | | - | -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 | - | | - Then exactly 1 result is returned - And result has attributes address - - When sending v1/reverse at 47.14122383,9.52169581334 - | extratags | - | | - Then exactly 1 result is returned - And result has attributes extratags - - When sending v1/reverse at 47.14122383,9.52169581334 - | namedetails | - | | - Then exactly 1 result is returned - And result has attributes namedetails - - When sending v1/reverse at 47.14122383,9.52169581334 - | polygon_geojson | - | | - Then exactly 1 result is returned - And result has attributes geojson - - When sending v1/reverse at 47.14122383,9.52169581334 - | polygon_kml | - | | - Then exactly 1 result is returned - And result has attributes geokml - - When sending v1/reverse at 47.14122383,9.52169581334 - | polygon_svg | - | | - Then exactly 1 result is returned - And result has attributes svg - - When sending v1/reverse at 47.14122383,9.52169581334 - | polygon_text | - | | - 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 - | json_callback | - | foo | - Then the result is valid - - 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 | | - 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 | - | | | - 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 - 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 - | 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 - | zoom | extratags | - | 14 | 1 | - Then results contain in field extratags - | wikidata | - | Q4529531 | - - Examples: - | format | - | json | - | jsonv2 | - | xml | - | geojson | - - diff --git a/test/bdd/api/reverse/v1_xml.feature b/test/bdd/api/reverse/v1_xml.feature deleted file mode 100644 index 95e7478c..00000000 --- a/test/bdd/api/reverse/v1_xml.feature +++ /dev/null @@ -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 | - | | - Then result has attributes place_id - Then result has 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 | - | ^9.5039\d*,47.0657\d* 9.5040\d*,47.0657\d* 9.5040\d*,47.065\d* 9.5054\d*,47.0661\d* | diff --git a/test/bdd/api/search/geocodejson.feature b/test/bdd/api/search/geocodejson.feature deleted file mode 100644 index 271ec10c..00000000 --- a/test/bdd/api/search/geocodejson.feature +++ /dev/null @@ -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 | diff --git a/test/bdd/api/search/language.feature b/test/bdd/api/search/language.feature deleted file mode 100644 index fe14cdbe..00000000 --- a/test/bdd/api/search/language.feature +++ /dev/null @@ -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 | diff --git a/test/bdd/api/search/params.feature b/test/bdd/api/search/params.feature deleted file mode 100644 index e77a00d2..00000000 --- a/test/bdd/api/search/params.feature +++ /dev/null @@ -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 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 | - | | - 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 | - | | - 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 | | - 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 | | - 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 | | - Then a HTTP 400 is returned - - Examples: - | th | - | x | - | ;; | - | 1m | - - Scenario Outline: Search with extratags - When sending search query "Landstr" - | extratags | - | 1 | - Then result has attributes extratags - - Examples: - | format | - | xml | - | json | - | jsonv2 | - | geojson | - - Scenario Outline: Search with namedetails - When sending 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 search query "triesenberg" - | polygon_text | - | 1 | - Then result has attributes - - Examples: - | format | response_attribute | - | xml | geotext | - | json | geotext | - | jsonv2 | geotext | - - Scenario Outline: Search result contains SVG geometry - When sending search query "triesenberg" - | polygon_svg | - | 1 | - Then result has attributes - - Examples: - | format | response_attribute | - | xml | geosvg | - | json | svg | - | jsonv2 | svg | - - Scenario Outline: Search result contains KML geometry - When sending search query "triesenberg" - | polygon_kml | - | 1 | - Then result has attributes - - Examples: - | format | response_attribute | - | xml | geokml | - | json | geokml | - | jsonv2 | geokml | - - Scenario Outline: Search result contains GEOJSON geometry - When sending search query "triesenberg" - | polygon_geojson | - | 1 | - Then result has attributes - - 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 - - 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 | diff --git a/test/bdd/api/search/queries.feature b/test/bdd/api/search/queries.feature deleted file mode 100644 index 3b06af78..00000000 --- a/test/bdd/api/search/queries.feature +++ /dev/null @@ -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 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 | - | | - Then result addresses contain - | country_code | - | | - - 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 "" - 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 diff --git a/test/bdd/api/search/simple.feature b/test/bdd/api/search/simple.feature deleted file mode 100644 index 655c639b..00000000 --- a/test/bdd/api/search/simple.feature +++ /dev/null @@ -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 | - | | | - Then at least 1 result is returned - When sending xml search query "Vaduz" - | param | value | - | | | - Then at least 1 result is returned - When sending json search query "Vaduz" - | param | value | - | | | - Then at least 1 result is returned - When sending jsonv2 search query "Vaduz" - | param | value | - | | | - Then at least 1 result is returned - When sending geojson search query "Vaduz" - | param | value | - | | | - Then at least 1 result is returned - When sending geocodejson search query "Vaduz" - | param | 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 "" - Then the result is valid json - When sending xml search query "" - Then the result is valid xml - When sending json search query "" - Then the result is valid json - When sending jsonv2 search query "" - Then the result is valid json - When sending geojson search 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"xvbyxcssdex" - Then result header contains - | attr | value | - | querystring | xfdghn&zxn"xvbyxcssdex | - | 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 | | - Then result header contains - | attr | value | - | json_func | | - - 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 | | - 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 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 "" - Then a HTTP 200 is returned - - Examples: - | query | - | Liechtenstein | - | Triesen | - | Pfarrkirche | - | Landstr 27 Steinort, Triesenberg, 9495 | - | 9497 | - | restaurant in triesen | diff --git a/test/bdd/api/search/structured.feature b/test/bdd/api/search/structured.feature deleted file mode 100644 index 1d609923..00000000 --- a/test/bdd/api/search/structured.feature +++ /dev/null @@ -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 | - diff --git a/test/bdd/api/status/failures.feature b/test/bdd/api/status/failures.feature deleted file mode 100644 index 70e9589a..00000000 --- a/test/bdd/api/status/failures.feature +++ /dev/null @@ -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 diff --git a/test/bdd/api/status/simple.feature b/test/bdd/api/status/simple.feature deleted file mode 100644 index 993fa1ec..00000000 --- a/test/bdd/api/status/simple.feature +++ /dev/null @@ -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 diff --git a/test/bdd/conftest.py b/test/bdd/conftest.py new file mode 100644 index 00000000..97d09dee --- /dev/null +++ b/test/bdd/conftest.py @@ -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[\d.-]*),(?P[\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.*)")?'), + 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[a-z ]+) (?P\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.*)'), + 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\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.*)'), + 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.*)'), + 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 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.*)'), + 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.*)'), + 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\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\d+) contains(?: in field (?P\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 diff --git a/test/bdd/features/api/details/language.feature b/test/bdd/features/api/details/language.feature new file mode 100644 index 00000000..f15b4ffb --- /dev/null +++ b/test/bdd/features/api/details/language.feature @@ -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 | diff --git a/test/bdd/features/api/details/params.feature b/test/bdd/features/api/details/params.feature new file mode 100644 index 00000000..1212e70a --- /dev/null +++ b/test/bdd/features/api/details/params.feature @@ -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 | + | | | 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 | + | | | 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 | + | | + + Examples: + | type | id | geometry | + | W | 297699560 | LineString | + | W | 243055645 | Polygon | + | W | 243055716 | Polygon | + | W | 43327921 | LineString | + + diff --git a/test/bdd/features/api/details/simple.feature b/test/bdd/features/api/details/simple.feature new file mode 100644 index 00000000..4010d0ff --- /dev/null +++ b/test/bdd/features/api/details/simple.feature @@ -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 | + | | | + Then a HTTP 200 is returned + And the result is valid json + And the result contains + | osm_type | osm_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 | | + Then a HTTP 200 is returned + And the result is valid json + And the result contains + | osm_type | osm_id | category | + | N | 300209696 | | + + Examples: + | class | + | tourism | + | mountain_pass | + + Scenario: Details request without osmtype + When sending v1/details + | osmid | + | | + 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 | + | | | 1 | + Then a HTTP 200 is returned + And the result is valid html + + Examples: + | type | id | + | N | 5484325405 | + | W | 43327921 | + | R | 123924 | + diff --git a/test/bdd/features/api/lookup/simple.feature b/test/bdd/features/api/lookup/simple.feature new file mode 100644 index 00000000..6ecb88fa --- /dev/null +++ b/test/bdd/features/api/lookup/simple.feature @@ -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 + | osm_ids | + | N5484325405,W43327921,,R123924,X99,N0 | + Then a HTTP 200 is returned + And the result is valid + 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 + | osm_ids | + | N5484325405,W43327921 | + Then the result is valid + 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 + | 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 + 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 | diff --git a/test/bdd/features/api/reverse/geometry.feature b/test/bdd/features/api/reverse/geometry.feature new file mode 100644 index 00000000..a04b4e01 --- /dev/null +++ b/test/bdd/features/api/reverse/geometry.feature @@ -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 | diff --git a/test/bdd/features/api/reverse/language.feature b/test/bdd/features/api/reverse/language.feature new file mode 100644 index 00000000..927f258c --- /dev/null +++ b/test/bdd/features/api/reverse/language.feature @@ -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 | diff --git a/test/bdd/api/reverse/layers.feature b/test/bdd/features/api/reverse/layers.feature similarity index 80% rename from test/bdd/api/reverse/layers.feature rename to test/bdd/features/api/reverse/layers.feature index f1885f0e..809d7e3d 100644 --- a/test/bdd/api/reverse/layers.feature +++ b/test/bdd/features/api/reverse/layers.feature @@ -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 | | | - Then results contain + Then the result contains | 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 | | | - Then results contain + Then the result contains | 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 | | - Then results contain + Then the result contains | 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 | | | - Then results contain + Then the result contains | category | type | | | | diff --git a/test/bdd/features/api/reverse/queries.feature b/test/bdd/features/api/reverse/queries.feature new file mode 100644 index 00000000..eb1ae75f --- /dev/null +++ b/test/bdd/features/api/reverse/queries.feature @@ -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 | + | | + 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 | diff --git a/test/bdd/features/api/reverse/v1_geocodejson.feature b/test/bdd/features/api/reverse/v1_geocodejson.feature new file mode 100644 index 00000000..40be511d --- /dev/null +++ b/test/bdd/features/api/reverse/v1_geocodejson.feature @@ -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 | + | | | + 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 | | + 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 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 | | + | 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 | diff --git a/test/bdd/features/api/reverse/v1_geojson.feature b/test/bdd/features/api/reverse/v1_geojson.feature new file mode 100644 index 00000000..83f98e65 --- /dev/null +++ b/test/bdd/features/api/reverse/v1_geojson.feature @@ -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 | + | | | + 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 | | + 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 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 | | + | 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 | diff --git a/test/bdd/features/api/reverse/v1_json.feature b/test/bdd/features/api/reverse/v1_json.feature new file mode 100644 index 00000000..ca361033 --- /dev/null +++ b/test/bdd/features/api/reverse/v1_json.feature @@ -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 | + | | | + 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 | + | | | + 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 | | + Then a HTTP 200 is returned + And the result is valid json + And the result has address + When sending v1/reverse with format jsonv2 + | lat | lon | addressdetails | + | 47.066 | 9.504 | | + Then a HTTP 200 is returned + And the result is valid json + And the result has address + + Examples: + | has_address | attributes | + | 1 | attributes | + | 0 | no attributes | + + Scenario Outline: Reverse json - Simple OSM result + When sending v1/reverse with 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 + | 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 + | 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 + | 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 + | 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 + | 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 | + | 9.5039\d*,47.0657\d* 9.5040\d*,47.0657\d* 9.5040\d*,47.065\d* 9.5054\d*,47.0661\d* | + + Examples: + | format | + | json | + | jsonv2 | diff --git a/test/bdd/features/api/reverse/v1_params.feature b/test/bdd/features/api/reverse/v1_params.feature new file mode 100644 index 00000000..8708a10a --- /dev/null +++ b/test/bdd/features/api/reverse/v1_params.feature @@ -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 | + | | | + 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 | + | | 3.4 | + | 3.4 | | + | -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 | | + 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 | | + 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 | | + 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 | | + 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 | + | | | 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 + | lat | lon | + | 47.1068011 | 9.52810091 | + Then a HTTP 200 is returned + And the result is valid + 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 + | lat | lon | zoom | namedetails | + | 47.14052 | 9.52202 | 14 | 1 | + Then a HTTP 200 is returned + And the result is valid + 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 + | lat | lon | zoom | extratags | + | 47.14052 | 9.52202 | 14 | 1 | + Then a HTTP 200 is returned + And the result is valid + And the result contains in field extratags + | wikidata | + | Q4529531 | + + Examples: + | format | outformat | + | json | json | + | jsonv2 | json | + | xml | xml | diff --git a/test/bdd/features/api/reverse/v1_xml.feature b/test/bdd/features/api/reverse/v1_xml.feature new file mode 100644 index 00000000..e4a25ff3 --- /dev/null +++ b/test/bdd/features/api/reverse/v1_xml.feature @@ -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 | + | | | + 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 | | + Then a HTTP 200 is returned + And the result is valid xml + And the result has attributes place_id + And the result has 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 | + | 9.5039\d*,47.0657\d* 9.5040\d*,47.0657\d* 9.5040\d*,47.065\d* 9.5054\d*,47.0661\d* | diff --git a/test/bdd/features/api/search/language.feature b/test/bdd/features/api/search/language.feature new file mode 100644 index 00000000..ead4f88f --- /dev/null +++ b/test/bdd/features/api/search/language.feature @@ -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 | diff --git a/test/bdd/features/api/search/params.feature b/test/bdd/features/api/search/params.feature new file mode 100644 index 00000000..d2f41f48 --- /dev/null +++ b/test/bdd/features/api/search/params.feature @@ -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 + | q | + | Vaduz | + Then a HTTP 200 is returned + And the result is valid json + And all results have attributes place_id,licence,,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 + | q | addressdetails | + | Triesen | 1 | + Then a HTTP 200 is returned + And the result is valid + 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 | | + 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 | | + 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 | | + 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 | | + 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 | | + Then a HTTP 400 is returned + + Examples: + | th | + | x | + | ;; | + | 1m | + + Scenario Outline: Search with extratags + When sending v1/search with format + | q | extratags | + | Landstr | 1 | + Then a HTTP 200 is returned + And the result is valid + 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 + | q | namedetails | + | Landstr | 1 | + Then a HTTP 200 is returned + And the result is valid + 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 + | q | | + | Triesenberg | 1 | + Then a HTTP 200 is returned + And the result is valid + And more than 0 results are returned + And all results have attributes + + 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 | | + | 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 + + Examples: + | param | response_attribute | + | polygon_text | geotext | + | polygon_svg | svg | + | polygon_kml | geokml | diff --git a/test/bdd/api/search/postcode.feature b/test/bdd/features/api/search/postcode.feature similarity index 58% rename from test/bdd/api/search/postcode.feature rename to test/bdd/features/api/search/postcode.feature index fb722862..56242ec3 100644 --- a/test/bdd/api/search/postcode.feature +++ b/test/bdd/features/api/search/postcode.feature @@ -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 | diff --git a/test/bdd/features/api/search/queries.feature b/test/bdd/features/api/search/queries.feature new file mode 100644 index 00000000..8453b53b --- /dev/null +++ b/test/bdd/features/api/search/queries.feature @@ -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 + | q | addressdetails | + | Gnalpstrasse 0 | 1 | + Then a HTTP 200 is returned + And the result is valid + 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 | + | | + Then all results contain + | address+country_code | + | | + + 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 "" + 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 | diff --git a/test/bdd/features/api/search/simple.feature b/test/bdd/features/api/search/simple.feature new file mode 100644 index 00000000..3dc76922 --- /dev/null +++ b/test/bdd/features/api/search/simple.feature @@ -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 | + | | + 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"xvbyxcssdex | + Then a HTTP 200 is returned + And the result is valid xml + Then the result metadata contains + | param | value | + | querystring | xfdghn&zxn"xvbyxcssdex | + | 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 | | + 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 + | q | + | YHlERzzx | + Then a HTTP 200 is returned + And the result is valid + 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 | + | | 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 | diff --git a/test/bdd/features/api/search/structured.feature b/test/bdd/features/api/search/structured.feature new file mode 100644 index 00000000..60f0f309 --- /dev/null +++ b/test/bdd/features/api/search/structured.feature @@ -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 | + diff --git a/test/bdd/features/api/search/v1_geocodejson.feature b/test/bdd/features/api/search/v1_geocodejson.feature new file mode 100644 index 00000000..99fff0e4 --- /dev/null +++ b/test/bdd/features/api/search/v1_geocodejson.feature @@ -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 | diff --git a/test/bdd/features/api/status/failures.feature b/test/bdd/features/api/status/failures.feature new file mode 100644 index 00000000..b66bf324 --- /dev/null +++ b/test/bdd/features/api/status/failures.feature @@ -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 diff --git a/test/bdd/features/api/status/simple.feature b/test/bdd/features/api/status/simple.feature new file mode 100644 index 00000000..23ba0934 --- /dev/null +++ b/test/bdd/features/api/status/simple.feature @@ -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 | diff --git a/test/bdd/test_api.py b/test/bdd/test_api.py new file mode 100644 index 00000000..5ace7b94 --- /dev/null +++ b/test/bdd/test_api.py @@ -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\S+)(?: with format (?P\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\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.*)"')) +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\S+)(?: with (?P\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') diff --git a/test/bdd/utils/__init__.py b/test/bdd/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/bdd/utils/api_result.py b/test/bdd/utils/api_result.py new file mode 100644 index 00000000..d21697e2 --- /dev/null +++ b/test/bdd/utils/api_result.py @@ -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) diff --git a/test/bdd/utils/api_runner.py b/test/bdd/utils/api_runner.py new file mode 100644 index 00000000..d57067b3 --- /dev/null +++ b/test/bdd/utils/api_runner.py @@ -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 diff --git a/test/bdd/utils/checks.py b/test/bdd/utils/checks.py new file mode 100644 index 00000000..22c538f9 --- /dev/null +++ b/test/bdd/utils/checks.py @@ -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})" diff --git a/test/bdd/utils/db.py b/test/bdd/utils/db.py new file mode 100644 index 00000000..661112ee --- /dev/null +++ b/test/bdd/utils/db.py @@ -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 -- 2.39.5