diff --git a/ors-api/src/test/java/org/heigit/ors/apitests/snapping/ParamsTest.java b/ors-api/src/test/java/org/heigit/ors/apitests/snapping/ParamsTest.java index 63bd52d096..62e64c4f4a 100644 --- a/ors-api/src/test/java/org/heigit/ors/apitests/snapping/ParamsTest.java +++ b/ors-api/src/test/java/org/heigit/ors/apitests/snapping/ParamsTest.java @@ -1,7 +1,6 @@ package org.heigit.ors.apitests.snapping; import io.restassured.response.ValidatableResponse; -import io.restassured.specification.RequestSpecification; import org.apache.commons.lang3.StringUtils; import org.hamcrest.Matchers; import org.heigit.ors.apitests.common.EndPointAnnotation; @@ -9,6 +8,7 @@ import org.heigit.ors.apitests.common.VersionAnnotation; import org.json.JSONArray; import org.json.JSONObject; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -51,17 +51,26 @@ private static JSONArray fakeLocations(int maximumSize) { * * @return A JSONArray containing valid coordinates for testing. */ - public static JSONArray validLocations() { - JSONArray coordsShort = new JSONArray(); + private static JSONArray validLocations() { + // Create correct test locations with valid coordinates + JSONArray correctTestLocations = new JSONArray(); JSONArray coord1 = new JSONArray(); coord1.put(8.680916); coord1.put(49.410973); - coordsShort.put(coord1); + correctTestLocations.put(coord1); JSONArray coord2 = new JSONArray(); coord2.put(8.687782); - coord2.put(49.4246); - coordsShort.put(coord2); - return coordsShort; + coord2.put(49.424597); + correctTestLocations.put(coord2); + return correctTestLocations; + } + + + private static JSONObject validBody() { + JSONObject body = new JSONObject() + .put("locations", validLocations()) + .put("maximum_search_radius", "300"); + return body; } /** @@ -78,44 +87,127 @@ public static JSONArray validLocations() { */ public static Stream snappingEndpointSuccessTestProvider() { return Stream.of( - Arguments.of(new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "-1"), true, false, "json", "driving-hgv"), - Arguments.of(new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "0"), true, false, "json", "driving-hgv"), - Arguments.of(new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "1"), true, false, "json", "driving-hgv"), - Arguments.of(new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "10"), false, true, "json", "driving-hgv"), - Arguments.of(new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "300"), false, false, "json", "driving-hgv"), - Arguments.of(new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "400"), false, false, "json", "driving-hgv"), - Arguments.of(new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "1000"), false, false, "json", "driving-hgv"), - Arguments.of(new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "1000"), false, false, null, "driving-hgv") + Arguments.of(true, false, "driving-hgv", new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "-1")), + Arguments.of(true, false, "driving-hgv", new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "0")), + Arguments.of(true, false, "driving-hgv", new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "1")), + Arguments.of(false, true, "driving-hgv", new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "10")), + Arguments.of(false, false, "driving-hgv", new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "300")), + Arguments.of(false, false, "driving-hgv", new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "400")), + Arguments.of(false, false, "driving-hgv", new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "1000")), + Arguments.of(false, false, "driving-hgv", new JSONObject().put("locations", validLocations()).put("maximum_search_radius", "1000")) ); } /** * Parameterized test method for testing various scenarios in the Snapping Endpoint. * - * @param body The request body (JSONObject). - * @param emptyResult Boolean flag indicating whether an empty result is expected. - * @param partiallyEmptyResult Boolean flag indicating whether a partially empty result is expected. - * @param endPoint The endpoint type (String). - * @param profile The routing profile type (String). + * @param expectEmptyResult Boolean flag indicating whether an empty result is expected. + * @param expectPartiallyEmptyResult Boolean flag indicating whether a partially empty result is expected. + * @param body The request body (JSONObject). + * @param profile The routing profile type (String). */ @ParameterizedTest @MethodSource("snappingEndpointSuccessTestProvider") - void testSnappingSuccess(JSONObject body, Boolean emptyResult, Boolean partiallyEmptyResult, String endPoint, String profile) { + void testSnappingSuccessJson(Boolean expectEmptyResult, Boolean expectPartiallyEmptyResult, String profile, JSONObject body) { + String endPoint = "json"; + ValidatableResponse result = doRequestAndExceptSuccess(body, profile, endPoint); + validateJsonResponse(expectEmptyResult, expectPartiallyEmptyResult, result); + } - RequestSpecification requestSpecification = given() - .headers(jsonContent); + @Test + void testMissingPathParameterFormat_defaultsToJson() { + ValidatableResponse result = doRequestAndExceptSuccess(validBody(), "driving-hgv", null); + validateJsonResponse(false, false, result); + } - if (profile != null) - requestSpecification = requestSpecification.pathParam("profile", profile); + private static void validateJsonResponse(Boolean expectEmptyResult, Boolean expectPartiallyEmptyResult, ValidatableResponse result) { + boolean foundValidLocation = false; + boolean foundInvalidLocation = false; + + result.body("any { it.key == 'locations' }", is(true)); - String url = getEndPointPath(); - if (StringUtils.isNotBlank(profile)) - url = url + "/{profile}"; + // Iterate over the locations array and check the types of the values + ArrayList locations = result.extract().jsonPath().get("locations"); + for (int i = 0; i < locations.size(); i++) { + // if empty result is expected, check if the locations array is empty + if (expectEmptyResult) { + assertNull(result.extract().jsonPath().get("locations[" + i + "].location[0]")); + foundValidLocation = true; + foundInvalidLocation = true; + } else if (expectPartiallyEmptyResult && !foundInvalidLocation && result.extract().jsonPath().get("locations[" + i + "]") == null) { + foundInvalidLocation = true; + } else { + // Type expectations + assertEquals(Float.class, result.extract().jsonPath().get("locations[" + i + "].location[0]").getClass()); + assertEquals(Float.class, result.extract().jsonPath().get("locations[" + i + "].location[1]").getClass()); + assertEquals(Float.class, result.extract().jsonPath().get("locations[" + i + "].snapped_distance").getClass()); + // If name is in the response, check the type + if (result.extract().jsonPath().get("locations[" + i + "].name") != null) + assertEquals(String.class, result.extract().jsonPath().get("locations[" + i + "].name").getClass()); + foundValidLocation = true; + } + } + assertTrue(foundValidLocation); + if (expectPartiallyEmptyResult) + assertTrue(foundInvalidLocation); + } + + /** + * Parameterized test method for testing various scenarios in the Snapping Endpoint. + * + * @param expectEmptyResult Boolean flag indicating whether an empty result is expected. + * @param expectPartiallyEmptyResult Boolean flag indicating whether a partially empty result is expected. + * @param body The request body (JSONObject). + * @param profile The routing profile type (String). + */ + @ParameterizedTest + @MethodSource("snappingEndpointSuccessTestProvider") + void testSnappingSuccessGeojson(Boolean expectEmptyResult, Boolean expectPartiallyEmptyResult, String profile, JSONObject body) { + String endPoint = "geojson"; + ValidatableResponse result = doRequestAndExceptSuccess(body, profile, endPoint); + + boolean foundValidLocation = false; + boolean foundInvalidLocation = false; + + result.body("any { it.key == 'features' }", is(true)); + result.body("any { it.key == 'type' }", is(true)); + + // Iterate over the features array and check the types of the values + ArrayList features = result.extract().jsonPath().get("features"); + for (int i = 0; i < features.size(); i++) { + // if empty result is expected, check if the features array is empty + if (expectEmptyResult) { + assertNull(result.extract().jsonPath().get("features[" + i + "].features[0]")); + foundValidLocation = true; + foundInvalidLocation = true; + } else if (expectPartiallyEmptyResult && !foundInvalidLocation && result.extract().jsonPath().get("features[" + i + "]") == null) { + foundInvalidLocation = true; + } else { + // Type expectations + assertEquals(Float.class, result.extract().jsonPath().get("features[" + i + "].geometry.coordinates[0]").getClass()); + assertEquals(Float.class, result.extract().jsonPath().get("features[" + i + "].geometry.coordinates[1]").getClass()); + assertEquals(Float.class, result.extract().jsonPath().get("features[" + i + "].properties.snapped_distance").getClass()); + // If name is in the response, check the type + if (result.extract().jsonPath().get("features[" + i + "].properties.name") != null) + assertEquals(String.class, result.extract().jsonPath().get("features[" + i + "].properties.name").getClass()); + foundValidLocation = true; + } + } + + assertTrue(foundValidLocation); + if (expectPartiallyEmptyResult) + assertTrue(foundInvalidLocation); + } + + private ValidatableResponse doRequestAndExceptSuccess(JSONObject body, String profile, String endPoint) { + String url = getEndPointPath() + "/{profile}"; if (StringUtils.isNotBlank(endPoint)) - url = url + "/" + endPoint; + url = "%s/%s".formatted(url, endPoint); - ValidatableResponse result = requestSpecification + ValidatableResponse result = given() + .headers(jsonContent) + .pathParam("profile", profile) .body(body.toString()) .when() .log().ifValidationFails() @@ -125,7 +217,6 @@ void testSnappingSuccess(JSONObject body, Boolean emptyResult, Boolean partially .statusCode(200); // Check if the response contains the expected keys - result.body("any { it.key == 'locations' }", is(true)); result.body("any { it.key == 'metadata' }", is(true)); result.body("metadata.containsKey('attribution')", is(true)); result.body("metadata.service", is("snap")); @@ -138,41 +229,13 @@ void testSnappingSuccess(JSONObject body, Boolean emptyResult, Boolean partially result.body("metadata.query.locations[1].size()", is(2)); result.body("metadata.query.profile", is(profile)); - if (body.get("maximum_search_radius") != "0") - result.body("metadata.query.maximum_search_radius", is(Float.parseFloat(body.get("maximum_search_radius").toString()))); - if (StringUtils.isNotBlank(endPoint)) result.body("metadata.query.format", is(endPoint)); - - boolean foundValidLocation = false; - boolean foundInvalidLocation = false; - - // Iterate over the locations array and check the types of the values - ArrayList locations = result.extract().jsonPath().get("locations"); - for (int i = 0; i < locations.size(); i++) { - // if empty result is expected, check if the locations array is empty - if (emptyResult) { - assertNull(result.extract().jsonPath().get("locations[" + i + "].location[0]")); - foundValidLocation = true; - foundInvalidLocation = true; - } else if (partiallyEmptyResult && !foundInvalidLocation && result.extract().jsonPath().get("locations[" + i + "]") == null) { - foundInvalidLocation = true; - } else { - // Type expectations - assertEquals(Float.class, result.extract().jsonPath().get("locations[" + i + "].location[0]").getClass()); - assertEquals(Float.class, result.extract().jsonPath().get("locations[" + i + "].location[1]").getClass()); - assertEquals(Float.class, result.extract().jsonPath().get("locations[" + i + "].snapped_distance").getClass()); - // If name is in the response, check the type - if (result.extract().jsonPath().get("locations[" + i + "].name") != null) - assertEquals(String.class, result.extract().jsonPath().get("locations[" + i + "].name").getClass()); - foundValidLocation = true; - } + if (body.get("maximum_search_radius") != "0") { + result.body("metadata.query.maximum_search_radius", is(Float.parseFloat(body.get("maximum_search_radius").toString()))); } - - assertTrue(foundValidLocation); - if (partiallyEmptyResult) - assertTrue(foundInvalidLocation); + return result; } @@ -210,54 +273,36 @@ public static Stream snappingEndpointExceptionTestProvider() { invalidCoord1.put(8.680916); invalidCoords.put(invalidCoord1); - // Create correct test locations with valid coordinates - JSONArray correctTestLocations = new JSONArray(); - JSONArray coord1 = new JSONArray(); - coord1.put(8.680916); - coord1.put(49.410973); - correctTestLocations.put(coord1); - JSONArray coord2 = new JSONArray(); - coord2.put(8.687782); - coord2.put(49.424597); - correctTestLocations.put(coord2); + JSONArray correctTestLocations = validLocations(); // Return a stream of test arguments return Stream.of( - //Check exception for missing profile and return type - Arguments.of(MISSING_PARAMETER, BAD_REQUEST, null, null, new JSONObject() - .put("locations", correctTestLocations).put("maximum_search_radius", "300")), - //Check exception for missing profile - "json" is interpreted as profile - Arguments.of(INVALID_PARAMETER_VALUE, BAD_REQUEST, "json", null, new JSONObject() - .put("locations", correctTestLocations).put("maximum_search_radius", "300")), - //Check exception for missing profile - "json" is interpreted as profile - Arguments.of(UNSUPPORTED_EXPORT_FORMAT, SC_NOT_ACCEPTABLE, "badExportFormat", "driving-car", new JSONObject() - .put("locations", correctTestLocations).put("maximum_search_radius", "300")), - Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "json", "driving-car", new JSONObject()), + Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject()), // Check exception for one fake location to ensure single locations are checked - Arguments.of(POINT_NOT_FOUND, NOT_FOUND, "json", "driving-car", new JSONObject() + Arguments.of(POINT_NOT_FOUND, NOT_FOUND, "driving-car", new JSONObject() .put("locations", oneFakeLocation)), // Check exception for ten fake locations to ensure multiple locations are checked - Arguments.of(POINT_NOT_FOUND, NOT_FOUND, "json", "driving-car", new JSONObject() + Arguments.of(POINT_NOT_FOUND, NOT_FOUND, "driving-car", new JSONObject() .put("locations", tenFakeLocations)), // Check exception for broken location to ensure invalid locations are checked - Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "json", "driving-car", new JSONObject() + Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject() .put("locations", brokenFakeLocation)), // Check exception for wrong profile to ensure invalid profiles are checked - Arguments.of(INVALID_PARAMETER_VALUE, BAD_REQUEST, "json", "driving-foo", new JSONObject() + Arguments.of(INVALID_PARAMETER_VALUE, BAD_REQUEST, "driving-foo", new JSONObject() .put("locations", correctTestLocations)), // Check exception for unknown parameter to ensure unknown parameters are checked - Arguments.of(UNKNOWN_PARAMETER, BAD_REQUEST, "json", "driving-car", new JSONObject() + Arguments.of(UNKNOWN_PARAMETER, BAD_REQUEST, "driving-car", new JSONObject() .put("locations", correctTestLocations).put("unknown", "unknown")), // Check exception for invalid locations parameter (only one ccordinate) - Arguments.of(INVALID_PARAMETER_VALUE, BAD_REQUEST, "json", "driving-car", new JSONObject() + Arguments.of(INVALID_PARAMETER_VALUE, BAD_REQUEST, "driving-car", new JSONObject() .put("locations", invalidCoords).put("maximum_search_radius", "300")), // Check exception for invalid locations parameter (only one ccordinate) - Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "json", "driving-car", new JSONObject() + Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject() .put("locations", "noJsonArray").put("maximum_search_radius", "300")), // Check exception for invalid maximum_search_radius - Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "json", "driving-car", new JSONObject() + Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject() .put("locations", correctTestLocations).put("maximum_search_radius", "notANumber")), // Check exception for missing locations parameter - Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "json", "driving-car", new JSONObject() + Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject() .put("maximum_search_radius", "300") )); } @@ -272,30 +317,84 @@ public static Stream snappingEndpointExceptionTestProvider() { */ @ParameterizedTest @MethodSource("snappingEndpointExceptionTestProvider") - void testSnappingExceptions(int expectedErrorCode, int expectedStatusCode, String endPoint, String profile, JSONObject body) { - - RequestSpecification requestSpecification = given() - .headers(jsonContent); - - if (profile != null) - requestSpecification = requestSpecification.pathParam("profile", profile); - - String url = getEndPointPath(); - if (StringUtils.isNotBlank(profile)) - url = url + "/{profile}"; + void testSnappingExceptionsJson(int expectedErrorCode, int expectedStatusCode, String profile, JSONObject body) { + doRequestAndExpectError(expectedErrorCode, expectedStatusCode, profile, "json", body); + } - if (StringUtils.isNotBlank(endPoint)) - url = url + "/" + endPoint; + /** + * Parameterized test method for testing various exception scenarios in the Snapping Endpoint. + * + * @param expectedErrorCode The expected error code for the test case (SnappingErrorCodes). + * @param expectedStatusCode The expected HTTP status code for the test case (StatusCode). + * @param profile The routing profile type (String). + * @param body The request body (JSONObject). + */ + @ParameterizedTest + @MethodSource("snappingEndpointExceptionTestProvider") + void testSnappingExceptionsGeojson(int expectedErrorCode, int expectedStatusCode, String profile, JSONObject body) { + doRequestAndExpectError(expectedErrorCode, expectedStatusCode, profile, "geojson", body); + } - requestSpecification + void doRequestAndExpectError(int expectedErrorCode, int expectedStatusCode, String profile, String endPoint, JSONObject body) { + given() + .headers(jsonContent) + .pathParam("profile", profile) .body(body.toString()) .when() .log().ifValidationFails() - .post(url) + .post(getEndPointPath() + "/{profile}/" + endPoint) .then() .log().ifValidationFails() .assertThat() .body("error.code", Matchers.is(expectedErrorCode)) .statusCode(expectedStatusCode); } + + @Test + void testMissingPathParametersProfileAndFormat() { + JSONObject body = validBody(); + + given() + .headers(jsonContent) + .body(body.toString()) + .when() + .log().ifValidationFails() + .post(getEndPointPath()) + .then() + .log().ifValidationFails() + .assertThat() + .body("error.code", Matchers.is(MISSING_PARAMETER)) + .statusCode(BAD_REQUEST); + } + + @Test + void testMissingPathParameterProfile() { + given() + .headers(jsonContent) + .body(validBody().toString()) + .when() + .log().ifValidationFails() + .post(getEndPointPath() + "/json") + .then() + .log().ifValidationFails() + .assertThat() + .body("error.code", Matchers.is(INVALID_PARAMETER_VALUE)) + .statusCode(BAD_REQUEST); + } + + @Test + void testBadExportFormat() { + given() + .headers(jsonContent) + .body(validBody().toString()) + .when() + .log().ifValidationFails() + .post(getEndPointPath() + "/driving-car/xml") + .then() + .log().ifValidationFails() + .assertThat() + .body("error.code", Matchers.is(UNSUPPORTED_EXPORT_FORMAT)) + .statusCode(SC_NOT_ACCEPTABLE); + } + }