From b5d4369c1871ba70bb235995d2e5e3f6a84e9595 Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 16 Nov 2023 10:38:14 +0100 Subject: [PATCH 01/31] WIP api implementation --- .../service/http/apis/WriteFeatureApi.java | 6 ++ .../http/tasks/WriteFeatureApiTask.java | 67 +++++++++++++++---- .../src/main/resources/static/openapi.yaml | 38 +++++++++++ 3 files changed, 98 insertions(+), 13 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java index 920bd310d..c73332b02 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java @@ -19,6 +19,7 @@ package com.here.naksha.app.service.http.apis; import static com.here.naksha.app.service.http.tasks.WriteFeatureApiTask.WriteFeatureApiReqType.CREATE_FEATURES; +import static com.here.naksha.app.service.http.tasks.WriteFeatureApiTask.WriteFeatureApiReqType.MODIFY_FEATURES; import com.here.naksha.app.service.http.NakshaHttpVerticle; import com.here.naksha.app.service.http.tasks.WriteFeatureApiTask; @@ -41,6 +42,7 @@ public WriteFeatureApi(final @NotNull NakshaHttpVerticle verticle) { @Override public void addOperations(final @NotNull RouterBuilder rb) { rb.operation("postFeatures").handler(this::createFeatures); + rb.operation("putFeatures").handler(this::createFeatures); } @Override @@ -50,6 +52,10 @@ private void createFeatures(final @NotNull RoutingContext routingContext) { startWriteFeatureApiTask(CREATE_FEATURES, routingContext); } + private void updateFeatures(final @NotNull RoutingContext routingContext) { + startWriteFeatureApiTask(MODIFY_FEATURES, routingContext); + } + private void startWriteFeatureApiTask(WriteFeatureApiReqType reqType, RoutingContext routingContext) { new WriteFeatureApiTask<>( reqType, verticle, naksha(), routingContext, verticle.createNakshaContext(routingContext)) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index 938838f78..de16748e5 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -24,6 +24,7 @@ import static com.here.naksha.app.service.http.apis.ApiParams.SPACE_ID; import static com.here.naksha.app.service.http.apis.ApiParams.pathParam; +import com.fasterxml.jackson.core.JsonProcessingException; import com.here.naksha.app.service.http.NakshaHttpVerticle; import com.here.naksha.app.service.models.FeatureCollectionRequest; import com.here.naksha.lib.core.INaksha; @@ -80,12 +81,11 @@ protected void init() {} logger.info("Received Http request {}", this.reqType); // Custom execute logic to process input API request based on reqType try { - switch (this.reqType) { - case CREATE_FEATURES: - return executeCreateFeatures(); - default: - return executeUnsupported(); - } + return switch (this.reqType) { + case CREATE_FEATURES -> executeCreateFeatures(); + case MODIFY_FEATURES -> executeUpdateFeatures(); + default -> executeUnsupported(); + }; } catch (Exception ex) { // unexpected exception logger.error("Exception processing Http request. ", ex); @@ -96,13 +96,7 @@ protected void init() {} private @NotNull XyzResponse executeCreateFeatures() throws Exception { // Deserialize input request - final FeatureCollectionRequest collectionRequest; - try (final Json json = Json.get()) { - final String bodyJson = routingContext.body().asString(); - collectionRequest = json.reader(ViewDeserialize.User.class) - .forType(FeatureCollectionRequest.class) - .readValue(bodyJson); - } + final FeatureCollectionRequest collectionRequest = featuresFromRequestBody(); final List features = (List) collectionRequest.getFeatures(); if (features.isEmpty()) { return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Can't create empty features"); @@ -137,4 +131,51 @@ protected void init() {} // transform WriteResult to Http FeatureCollection response return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class); } + + private @NotNull XyzResponse executeUpdateFeatures() throws Exception { + // Deserialize input request + final FeatureCollectionRequest collectionRequest = featuresFromRequestBody(); + final List features = (List) collectionRequest.getFeatures(); + if (features.isEmpty()) { + return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Can't update empty features"); + } + + // Parse API parameters + final String spaceId = pathParam(routingContext, SPACE_ID); + final QueryParameterList queryParams = (routingContext.request().query() != null) + ? new QueryParameterList(routingContext.request().query()) + : null; + final String prefixId = (queryParams != null) ? queryParams.getValueOf(PREFIX_ID, String.class) : null; + final List addTags = (queryParams != null) ? queryParams.collectAllOf(ADD_TAGS, String.class) : null; + final List removeTags = + (queryParams != null) ? queryParams.collectAllOf(REMOVE_TAGS, String.class) : null; + + // Validate parameters + if (spaceId == null || spaceId.isEmpty()) { + return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Missing spaceId parameter"); + } + + // as applicable, modify features based on parameters supplied + if (prefixId != null || addTags != null || removeTags != null) { + for (final XyzFeature feature : features) { + feature.setIdPrefix(prefixId); + feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true); + } + } + final WriteFeatures wrRequest = RequestHelper.createFeaturesRequest(spaceId, features); + + // Forward request to NH Space Storage writer instance + final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest); + // transform WriteResult to Http FeatureCollection response + return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class); + } + + private @NotNull FeatureCollectionRequest featuresFromRequestBody() throws JsonProcessingException { + try (final Json json = Json.get()) { + final String bodyJson = routingContext.body().asString(); + return json.reader(ViewDeserialize.User.class) + .forType(FeatureCollectionRequest.class) + .readValue(bodyJson); + } + } } diff --git a/here-naksha-app-service/src/main/resources/static/openapi.yaml b/here-naksha-app-service/src/main/resources/static/openapi.yaml index 748728554..8bdd19baf 100644 --- a/here-naksha-app-service/src/main/resources/static/openapi.yaml +++ b/here-naksha-app-service/src/main/resources/static/openapi.yaml @@ -476,6 +476,44 @@ paths: $ref: '#/components/responses/ErrorResponse504' '513': $ref: '#/components/responses/ErrorResponse513' + put: + tags: + - Write Features + summary: Update features in the space + description: > + This method allows to update features in the storage associated (directly or via event handler) with the space. + It ensures an atomic operation, so either all features will be created or none (in case of failure). + operationId: putFeatures + parameters: + - $ref: '#/components/parameters/SpaceId' + - $ref: '#/components/parameters/AddTags' + - $ref: '#/components/parameters/RemoveTags' + - $ref: '#/components/parameters/PrefixId' + requestBody: + $ref: '#/components/requestBodies/FeatureCollectionRequest' + responses: + '200': + $ref: '#/components/responses/FeatureCollectionModificationResponse' + '400': + $ref: '#/components/responses/ErrorResponse400' + '401': + $ref: '#/components/responses/ErrorResponse401' + '403': + $ref: '#/components/responses/ErrorResponse403' + '404': + $ref: '#/components/responses/ErrorResponse404' + '409': + $ref: '#/components/responses/ErrorResponse409' + '429': + $ref: '#/components/responses/ErrorResponse429' + '500': + $ref: '#/components/responses/ErrorResponse500' + '502': + $ref: '#/components/responses/ErrorResponse502' + '504': + $ref: '#/components/responses/ErrorResponse504' + '513': + $ref: '#/components/responses/ErrorResponse513' '/hub/spaces/{spaceId}/features/{featureId}': get: tags: From df5e30342986a399dbb7610c72273bbc11f31f62 Mon Sep 17 00:00:00 2001 From: phmai Date: Fri, 17 Nov 2023 16:15:53 +0100 Subject: [PATCH 02/31] implement update multiple features, one test --- .../service/http/apis/WriteFeatureApi.java | 2 +- .../http/tasks/WriteFeatureApiTask.java | 6 ++-- .../naksha/app/service/NakshaAppTest.java | 35 +++++++++++++++++-- .../response_no_geometry.json | 19 ++++++++++ .../TC0500_updateFeatures/update_request.json | 19 ++++++++++ .../lib/core/util/storage/RequestHelper.java | 17 +++++++++ 6 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java index c73332b02..83ec98629 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java @@ -42,7 +42,7 @@ public WriteFeatureApi(final @NotNull NakshaHttpVerticle verticle) { @Override public void addOperations(final @NotNull RouterBuilder rb) { rb.operation("postFeatures").handler(this::createFeatures); - rb.operation("putFeatures").handler(this::createFeatures); + rb.operation("putFeatures").handler(this::updateFeatures); } @Override diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index de16748e5..f7e472a04 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -145,7 +145,6 @@ protected void init() {} final QueryParameterList queryParams = (routingContext.request().query() != null) ? new QueryParameterList(routingContext.request().query()) : null; - final String prefixId = (queryParams != null) ? queryParams.getValueOf(PREFIX_ID, String.class) : null; final List addTags = (queryParams != null) ? queryParams.collectAllOf(ADD_TAGS, String.class) : null; final List removeTags = (queryParams != null) ? queryParams.collectAllOf(REMOVE_TAGS, String.class) : null; @@ -156,13 +155,12 @@ protected void init() {} } // as applicable, modify features based on parameters supplied - if (prefixId != null || addTags != null || removeTags != null) { + if (addTags != null || removeTags != null) { for (final XyzFeature feature : features) { - feature.setIdPrefix(prefixId); feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true); } } - final WriteFeatures wrRequest = RequestHelper.createFeaturesRequest(spaceId, features); + final WriteFeatures wrRequest = RequestHelper.updateFeaturesRequest(spaceId, features); // Forward request to NH Space Storage writer instance final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest); diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index 34963f364..6fced8e21 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -19,12 +19,11 @@ package com.here.naksha.app.service; import static com.here.naksha.app.common.NakshaAppInitializer.mockedNakshaApp; -import static com.here.naksha.app.common.TestUtil.HDR_STREAM_ID; -import static com.here.naksha.app.common.TestUtil.getHeader; -import static com.here.naksha.app.common.TestUtil.loadFileOrFail; +import static com.here.naksha.app.common.TestUtil.*; import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; +import com.here.naksha.lib.core.models.naksha.Space; import com.here.naksha.lib.hub.NakshaHubConfig; import com.here.naksha.lib.psql.PsqlStorage; import java.net.URI; @@ -808,6 +807,36 @@ void tc0407_testReadFeaturesWithCommaSeparatedIds() throws Exception { readFeaturesByIdsTests.tc0407_testReadFeaturesWithCommaSeparatedIds(); } + @Test + @Order(10) + void tc0500_testUpdateFeatures() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + String streamId; + HttpRequest request; + HttpResponse response; + + // Read request body + final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); + streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) + .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/" + space.getId() + "/features")) + .PUT(HttpRequest.BodyPublishers.ofString(bodyJson)) + .header(HDR_STREAM_ID, streamId) + .build(); + response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Get Feature response body doesn't match", expectedBodyPart, response.body(), JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + @AfterAll static void close() throws InterruptedException { if (app != null) { diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json new file mode 100644 index 000000000..6fd7815f7 --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json @@ -0,0 +1,19 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "id": "my-custom-id-301-1", + "type": "Feature", + "properties": { + "speedLimit": "50" + } + }, + { + "id": "my-custom-id-301-2", + "type": "Feature", + "properties": { + "speedLimit": "70" + } + } + ] +} diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json new file mode 100644 index 000000000..6fd7815f7 --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json @@ -0,0 +1,19 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "id": "my-custom-id-301-1", + "type": "Feature", + "properties": { + "speedLimit": "50" + } + }, + { + "id": "my-custom-id-301-2", + "type": "Feature", + "properties": { + "speedLimit": "70" + } + } + ] +} diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java index 621e6bbf3..f38f358f3 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java @@ -111,6 +111,23 @@ public class RequestHelper { return new WriteFeatures<>(collectionName, List.of(updateOp)); } + /** + * Helper method to create WriteFeatures request for updating multiple features. + * + * @param collectionName name of the storage collection + * @param features feature object array to be updated + * @param any object extending XyzFeature + * @return WriteFeatures request that can be used against IStorage methods + */ + public static @NotNull WriteFeatures updateFeaturesRequest( + final @NotNull String collectionName, final @NotNull List features) { + final List<@NotNull WriteOp> opList = new ArrayList<>(); + for (final T feature : features) { + opList.add(new WriteXyzOp<>(EWriteOp.UPDATE, feature)); + } + return new WriteFeatures<>(collectionName, opList); + } + /** * Helper method to create WriteFeatures request with given list of features. If silentIfExists is true, function internally sets * IfExists.RETAIN and IfConflict.RETAIN (to silently ignoring create operation, if feature already exists). If set to false, both flags From 155981eb7c272f43ca7d54bc9fba50b5bf1c1c31 Mon Sep 17 00:00:00 2001 From: phmai Date: Fri, 17 Nov 2023 16:52:42 +0100 Subject: [PATCH 03/31] wip --- .../http/tasks/WriteFeatureApiTask.java | 3 +- .../src/main/resources/static/openapi.yaml | 63 ++++++++++++++++++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index f7e472a04..b400444b3 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -52,7 +52,8 @@ public class WriteFeatureApiTask extends AbstractApiTask< public enum WriteFeatureApiReqType { CREATE_FEATURES, - MODIFY_FEATURES + MODIFY_FEATURES, + UPDATE_BY_ID } public WriteFeatureApiTask( diff --git a/here-naksha-app-service/src/main/resources/static/openapi.yaml b/here-naksha-app-service/src/main/resources/static/openapi.yaml index 8bdd19baf..f56fc5d35 100644 --- a/here-naksha-app-service/src/main/resources/static/openapi.yaml +++ b/here-naksha-app-service/src/main/resources/static/openapi.yaml @@ -488,7 +488,6 @@ paths: - $ref: '#/components/parameters/SpaceId' - $ref: '#/components/parameters/AddTags' - $ref: '#/components/parameters/RemoveTags' - - $ref: '#/components/parameters/PrefixId' requestBody: $ref: '#/components/requestBodies/FeatureCollectionRequest' responses: @@ -535,6 +534,42 @@ paths: $ref: '#/components/responses/ErrorResponse404' '513': $ref: '#/components/responses/ErrorResponse513' + put: + tags: + - Write Features + summary: Update feature with specified ID in the space + description: > + This method allows to update one feature in the storage associated (directly or via event handler) with the space. + operationId: putFeature + parameters: + - $ref: '#/components/parameters/SpaceId' + - $ref: '#/components/parameters/AddTags' + - $ref: '#/components/parameters/RemoveTags' + requestBody: + $ref: '#/components/requestBodies/FeatureRequest' + responses: + '200': + $ref: '#/components/responses/FeatureModificationResponse' + '400': + $ref: '#/components/responses/ErrorResponse400' + '401': + $ref: '#/components/responses/ErrorResponse401' + '403': + $ref: '#/components/responses/ErrorResponse403' + '404': + $ref: '#/components/responses/ErrorResponse404' + '409': + $ref: '#/components/responses/ErrorResponse409' + '429': + $ref: '#/components/responses/ErrorResponse429' + '500': + $ref: '#/components/responses/ErrorResponse500' + '502': + $ref: '#/components/responses/ErrorResponse502' + '504': + $ref: '#/components/responses/ErrorResponse504' + '513': + $ref: '#/components/responses/ErrorResponse513' components: securitySchemes: AccessToken: @@ -601,8 +636,32 @@ components: schema: type: string requestBodies: + FeatureRequest: + description: A request detailing one specific feature + required: true + content: + application/geo+json: + schema: + $ref: '#/components/schemas/Feature' + example: + type: "Feature" + geometry: + type: "Point" + coordinates: + - 8.68872 + - 50.0561 + - 292.94377758 + properties: + name: Anfield + '@ns:com:here:xyz': + tags: + - football + - stadium + amenity: Football Stadium + capacity: 54074 + description: Home of Liverpool Football Club FeatureCollectionRequest: - description: An FeatureCollection request holding list of Feature objects. + description: A FeatureCollection request holding list of Feature objects. required: true content: application/geo+json: From e93382ce05f1d5cc72da3ce300abc360bb309f40 Mon Sep 17 00:00:00 2001 From: phmai Date: Mon, 20 Nov 2023 14:28:06 +0100 Subject: [PATCH 04/31] update one feature by id --- .../service/http/apis/WriteFeatureApi.java | 8 ++- .../http/tasks/WriteFeatureApiTask.java | 56 +++++++++++++++++-- .../src/main/resources/static/openapi.yaml | 8 +-- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java index 83ec98629..dc3888f98 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java @@ -18,8 +18,7 @@ */ package com.here.naksha.app.service.http.apis; -import static com.here.naksha.app.service.http.tasks.WriteFeatureApiTask.WriteFeatureApiReqType.CREATE_FEATURES; -import static com.here.naksha.app.service.http.tasks.WriteFeatureApiTask.WriteFeatureApiReqType.MODIFY_FEATURES; +import static com.here.naksha.app.service.http.tasks.WriteFeatureApiTask.WriteFeatureApiReqType.*; import com.here.naksha.app.service.http.NakshaHttpVerticle; import com.here.naksha.app.service.http.tasks.WriteFeatureApiTask; @@ -43,6 +42,7 @@ public WriteFeatureApi(final @NotNull NakshaHttpVerticle verticle) { public void addOperations(final @NotNull RouterBuilder rb) { rb.operation("postFeatures").handler(this::createFeatures); rb.operation("putFeatures").handler(this::updateFeatures); + rb.operation("putFeature").handler(this::updateFeature); } @Override @@ -56,6 +56,10 @@ private void updateFeatures(final @NotNull RoutingContext routingContext) { startWriteFeatureApiTask(MODIFY_FEATURES, routingContext); } + private void updateFeature(final @NotNull RoutingContext routingContext) { + startWriteFeatureApiTask(UPDATE_BY_ID, routingContext); + } + private void startWriteFeatureApiTask(WriteFeatureApiReqType reqType, RoutingContext routingContext) { new WriteFeatureApiTask<>( reqType, verticle, naksha(), routingContext, verticle.createNakshaContext(routingContext)) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index b400444b3..f631eeb60 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -18,11 +18,7 @@ */ package com.here.naksha.app.service.http.tasks; -import static com.here.naksha.app.service.http.apis.ApiParams.ADD_TAGS; -import static com.here.naksha.app.service.http.apis.ApiParams.PREFIX_ID; -import static com.here.naksha.app.service.http.apis.ApiParams.REMOVE_TAGS; -import static com.here.naksha.app.service.http.apis.ApiParams.SPACE_ID; -import static com.here.naksha.app.service.http.apis.ApiParams.pathParam; +import static com.here.naksha.app.service.http.apis.ApiParams.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.here.naksha.app.service.http.NakshaHttpVerticle; @@ -39,6 +35,7 @@ import com.here.naksha.lib.core.util.json.Json; import com.here.naksha.lib.core.util.storage.RequestHelper; import com.here.naksha.lib.core.view.ViewDeserialize; +import com.here.naksha.lib.core.view.ViewDeserialize.User; import io.vertx.ext.web.RoutingContext; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -85,6 +82,7 @@ protected void init() {} return switch (this.reqType) { case CREATE_FEATURES -> executeCreateFeatures(); case MODIFY_FEATURES -> executeUpdateFeatures(); + case UPDATE_BY_ID -> executeUpdateFeature(); default -> executeUnsupported(); }; } catch (Exception ex) { @@ -169,6 +167,54 @@ protected void init() {} return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class); } + private @NotNull XyzResponse executeUpdateFeature() throws Exception { + // Deserialize input request + XyzFeature feature; + try (final Json json = Json.get()) { + final String bodyJson = routingContext.body().asString(); + feature = json.reader(User.class).forType(XyzFeature.class).readValue(bodyJson); + } + + // Parse API parameters + final String spaceId = pathParam(routingContext, SPACE_ID); + final String featureId = pathParam(routingContext, FEATURE_ID); + + final QueryParameterList queryParams = (routingContext.request().query() != null) + ? new QueryParameterList(routingContext.request().query()) + : null; + final List addTags = (queryParams != null) ? queryParams.collectAllOf(ADD_TAGS, String.class) : null; + final List removeTags = + (queryParams != null) ? queryParams.collectAllOf(REMOVE_TAGS, String.class) : null; + + // Validate parameters + if (spaceId == null || spaceId.isEmpty()) { + return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Missing spaceId parameter"); + } + + if (featureId == null || featureId.isEmpty()) { + return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Missing id parameter"); + } + + if (!featureId.equals(feature.getId())) { + return verticle.sendErrorResponse( + routingContext, + XyzError.ILLEGAL_ARGUMENT, + "URI path parameter id is not the same as in feature request body."); + } + + // as applicable, modify features based on parameters supplied + if (addTags != null || removeTags != null) { + feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true); + } + + final WriteFeatures wrRequest = RequestHelper.updateFeatureRequest(spaceId, feature); + + // Forward request to NH Space Storage writer instance + final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest); + // transform WriteResult to Http FeatureCollection response + return transformWriteResultToXyzFeatureResponse(wrResult, Storage.class); + } + private @NotNull FeatureCollectionRequest featuresFromRequestBody() throws JsonProcessingException { try (final Json json = Json.get()) { final String bodyJson = routingContext.body().asString(); diff --git a/here-naksha-app-service/src/main/resources/static/openapi.yaml b/here-naksha-app-service/src/main/resources/static/openapi.yaml index f56fc5d35..44adc8928 100644 --- a/here-naksha-app-service/src/main/resources/static/openapi.yaml +++ b/here-naksha-app-service/src/main/resources/static/openapi.yaml @@ -514,15 +514,15 @@ paths: '513': $ref: '#/components/responses/ErrorResponse513' '/hub/spaces/{spaceId}/features/{featureId}': + parameters: + - $ref: '#/components/parameters/SpaceId' + - $ref: '#/components/parameters/FeatureId' get: tags: - Read Features summary: Get a feature by ID description: Retrieves the feature with the provided identifier. operationId: getFeature - parameters: - - $ref: '#/components/parameters/SpaceId' - - $ref: '#/components/parameters/FeatureId' responses: '200': $ref: '#/components/responses/FeatureResponse' @@ -549,7 +549,7 @@ paths: $ref: '#/components/requestBodies/FeatureRequest' responses: '200': - $ref: '#/components/responses/FeatureModificationResponse' + $ref: '#/components/responses/FeatureResponse' '400': $ref: '#/components/responses/ErrorResponse400' '401': From b352f80410bc977b0a5fbf56ac9c81ce3a16096c Mon Sep 17 00:00:00 2001 From: phmai Date: Mon, 20 Nov 2023 16:24:53 +0100 Subject: [PATCH 05/31] add test --- .../http/tasks/WriteFeatureApiTask.java | 2 +- .../src/main/resources/static/openapi.yaml | 1 - .../naksha/app/service/NakshaAppTest.java | 38 ++++++++++++++++++- .../TC0501_updateOneFeatureById/response.json | 7 ++++ .../update_request.json | 7 ++++ 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/response.json create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request.json diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index f631eeb60..d925a2e06 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -192,7 +192,7 @@ protected void init() {} } if (featureId == null || featureId.isEmpty()) { - return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Missing id parameter"); + return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Missing featureId parameter"); } if (!featureId.equals(feature.getId())) { diff --git a/here-naksha-app-service/src/main/resources/static/openapi.yaml b/here-naksha-app-service/src/main/resources/static/openapi.yaml index 44adc8928..7112165f4 100644 --- a/here-naksha-app-service/src/main/resources/static/openapi.yaml +++ b/here-naksha-app-service/src/main/resources/static/openapi.yaml @@ -542,7 +542,6 @@ paths: This method allows to update one feature in the storage associated (directly or via event handler) with the space. operationId: putFeature parameters: - - $ref: '#/components/parameters/SpaceId' - $ref: '#/components/parameters/AddTags' - $ref: '#/components/parameters/RemoveTags' requestBody: diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index 6fced8e21..52cb19861 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -833,7 +833,43 @@ void tc0500_testUpdateFeatures() throws Exception { // Then: Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); JSONAssert.assertEquals( - "Get Feature response body doesn't match", expectedBodyPart, response.body(), JSONCompareMode.LENIENT); + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + @Test + @Order(11) + void tc0501_testUpdateFeatureById() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + String streamId; + HttpRequest request; + HttpResponse response; + + // Read request body + final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0501_updateOneFeatureById/response.json"); + streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) + .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/" + space.getId() + "/features/my-custom-id-301-1")) + .PUT(HttpRequest.BodyPublishers.ofString(bodyJson)) + .header(HDR_STREAM_ID, streamId) + .build(); + response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/response.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/response.json new file mode 100644 index 000000000..d63312d9b --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/response.json @@ -0,0 +1,7 @@ +{ + "id": "my-custom-id-301-1", + "type": "Feature", + "properties": { + "speedLimit": "30" + } +} \ No newline at end of file diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request.json new file mode 100644 index 000000000..d63312d9b --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request.json @@ -0,0 +1,7 @@ +{ + "id": "my-custom-id-301-1", + "type": "Feature", + "properties": { + "speedLimit": "30" + } +} \ No newline at end of file From 5435e53da64991830d55312d576d13efd100a1eb Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 21 Nov 2023 14:51:26 +0100 Subject: [PATCH 06/31] add test --- .../naksha/app/service/NakshaAppTest.java | 37 ++++++++++++++++++- ....json => update_request_and_response.json} | 0 .../request.json} | 2 +- .../response.json | 5 +++ 4 files changed, 41 insertions(+), 3 deletions(-) rename here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/{response.json => update_request_and_response.json} (100%) rename here-naksha-app-service/src/test/resources/unit_test_data/{TC0501_updateOneFeatureById/update_request.json => TC0502_updateFeatureWithWrongUriId/request.json} (69%) create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/response.json diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index 52cb19861..d0d07b956 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -849,10 +849,10 @@ void tc0501_testUpdateFeatureById() throws Exception { HttpResponse response; // Read request body - final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request.json"); + final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); // TODO: include geometry after Cursor-related changes -> final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0501_updateOneFeatureById/response.json"); + final String expectedBodyPart = bodyJson; streamId = UUID.randomUUID().toString(); // When: Create Features request is submitted to NakshaHub Space Storage instance @@ -873,6 +873,39 @@ void tc0501_testUpdateFeatureById() throws Exception { assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); } + @Test + @Order(11) + void tc0502_testUpdateFeatureByWrongUriId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + String streamId; + HttpRequest request; + HttpResponse response; + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); + streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) + .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/" + space.getId() + "/features/wrong-id")) + .PUT(HttpRequest.BodyPublishers.ofString(bodyJson)) + .header(HDR_STREAM_ID, streamId) + .build(); + response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + // Then: Perform assertions + assertEquals(409, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + @AfterAll static void close() throws InterruptedException { if (app != null) { diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/response.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request_and_response.json similarity index 100% rename from here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/response.json rename to here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request_and_response.json diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/request.json similarity index 69% rename from here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request.json rename to here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/request.json index d63312d9b..2b543f72e 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/request.json @@ -1,5 +1,5 @@ { - "id": "my-custom-id-301-1", + "id": "wrong-id", "type": "Feature", "properties": { "speedLimit": "30" diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/response.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/response.json new file mode 100644 index 000000000..a4eb75c5c --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/response.json @@ -0,0 +1,5 @@ +{ + "type": "ErrorResponse", + "error": "Conflict", + "errorMessage": "No feature found for id wrong-id" +} From e41044f6e216554bc9cc37b9d78f9b78d44d2b13 Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 21 Nov 2023 14:58:13 +0100 Subject: [PATCH 07/31] add test --- .../http/tasks/WriteFeatureApiTask.java | 2 +- .../naksha/app/service/NakshaAppTest.java | 33 +++++++++++++++++++ .../response.json | 5 +++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0503_updateFeatureMismatchingId/response.json diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index d925a2e06..a9aea54a2 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -199,7 +199,7 @@ protected void init() {} return verticle.sendErrorResponse( routingContext, XyzError.ILLEGAL_ARGUMENT, - "URI path parameter id is not the same as in feature request body."); + "URI path parameter featureId is not the same as id in feature request body."); } // as applicable, modify features based on parameters supplied diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index d0d07b956..ad7246819 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -906,6 +906,39 @@ void tc0502_testUpdateFeatureByWrongUriId() throws Exception { assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); } + @Test + @Order(11) + void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + String streamId; + HttpRequest request; + HttpResponse response; + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); + streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) + .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/" + space.getId() + "/features/my-custom-id-301-1")) + .PUT(HttpRequest.BodyPublishers.ofString(bodyJson)) + .header(HDR_STREAM_ID, streamId) + .build(); + response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + // Then: Perform assertions + assertEquals(400, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + @AfterAll static void close() throws InterruptedException { if (app != null) { diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0503_updateFeatureMismatchingId/response.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0503_updateFeatureMismatchingId/response.json new file mode 100644 index 000000000..a2ffefeae --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0503_updateFeatureMismatchingId/response.json @@ -0,0 +1,5 @@ +{ + "type": "ErrorResponse", + "error": "IllegalArgument", + "errorMessage": "URI path parameter featureId is not the same as id in feature request body." +} \ No newline at end of file From 1fc7552a3209e9899b99de015b9297915149852e Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 21 Nov 2023 16:46:31 +0100 Subject: [PATCH 08/31] align tests, autoformat --- .../app/service/http/NakshaHttpVerticle.java | 3 +- .../naksha/app/service/NakshaAppTest.java | 69 ++++--------------- 2 files changed, 15 insertions(+), 57 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/NakshaHttpVerticle.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/NakshaHttpVerticle.java index 83fc29980..1f8bae1d4 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/NakshaHttpVerticle.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/NakshaHttpVerticle.java @@ -338,8 +338,7 @@ private void notFoundHandler(final @NotNull RoutingContext routingContext) { * * @param routingContext The routing context. */ - private void onHeadersEnd(final @NotNull RoutingContext routingContext) { - } + private void onHeadersEnd(final @NotNull RoutingContext routingContext) {} /** * An end handler for the response. This will be called when the response is disposed to allow consistent cleanup of the response. diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index 4a6c41317..b5a2ed057 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -19,29 +19,17 @@ package com.here.naksha.app.service; import static com.here.naksha.app.common.NakshaAppInitializer.mockedNakshaApp; -import static com.here.naksha.app.common.TestUtil.HDR_STREAM_ID; -import static com.here.naksha.app.common.TestUtil.getHeader; -import static com.here.naksha.app.common.TestUtil.loadFileOrFail; -import static java.time.temporal.ChronoUnit.SECONDS; +import static com.here.naksha.app.common.TestUtil.*; import static org.junit.jupiter.api.Assertions.assertEquals; import com.here.naksha.app.common.NakshaTestWebClient; import com.here.naksha.lib.core.models.naksha.Space; import com.here.naksha.lib.hub.NakshaHubConfig; import com.here.naksha.lib.psql.PsqlStorage; -import java.net.URI; import java.net.URISyntaxException; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.time.Duration; import java.util.UUID; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.*; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; @@ -680,24 +668,16 @@ void tc0407_testReadFeaturesWithCommaSeparatedIds() throws Exception { @Order(10) void tc0500_testUpdateFeatures() throws Exception { // Test API : PUT /hub/spaces/{spaceId}/features - String streamId; - HttpRequest request; - HttpResponse response; - // Read request body final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); // TODO: include geometry after Cursor-related changes -> final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); - streamId = UUID.randomUUID().toString(); + final String streamId = UUID.randomUUID().toString(); // When: Create Features request is submitted to NakshaHub Space Storage instance - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/" + space.getId() + "/features")) - .PUT(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); // Then: Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -713,24 +693,17 @@ void tc0500_testUpdateFeatures() throws Exception { @Order(11) void tc0501_testUpdateFeatureById() throws Exception { // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - String streamId; - HttpRequest request; - HttpResponse response; // Read request body final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); // TODO: include geometry after Cursor-related changes -> final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); final String expectedBodyPart = bodyJson; - streamId = UUID.randomUUID().toString(); + final String streamId = UUID.randomUUID().toString(); // When: Create Features request is submitted to NakshaHub Space Storage instance - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/" + space.getId() + "/features/my-custom-id-301-1")) - .PUT(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); // Then: Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -746,24 +719,17 @@ void tc0501_testUpdateFeatureById() throws Exception { @Order(11) void tc0502_testUpdateFeatureByWrongUriId() throws Exception { // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - String streamId; - HttpRequest request; - HttpResponse response; // Read request body final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); // TODO: include geometry after Cursor-related changes -> final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); - streamId = UUID.randomUUID().toString(); + final String streamId = UUID.randomUUID().toString(); // When: Create Features request is submitted to NakshaHub Space Storage instance - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/" + space.getId() + "/features/wrong-id")) - .PUT(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); // Then: Perform assertions assertEquals(409, response.statusCode(), "ResCode mismatch"); @@ -779,24 +745,17 @@ void tc0502_testUpdateFeatureByWrongUriId() throws Exception { @Order(11) void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - String streamId; - HttpRequest request; - HttpResponse response; // Read request body final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); // TODO: include geometry after Cursor-related changes -> final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); - streamId = UUID.randomUUID().toString(); + final String streamId = UUID.randomUUID().toString(); // When: Create Features request is submitted to NakshaHub Space Storage instance - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/" + space.getId() + "/features/my-custom-id-301-1")) - .PUT(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); // Then: Perform assertions assertEquals(400, response.statusCode(), "ResCode mismatch"); From f812bf6c75564b474a6d3e42e476e7bc5e134f18 Mon Sep 17 00:00:00 2001 From: phmai Date: Wed, 22 Nov 2023 12:03:31 +0100 Subject: [PATCH 09/31] allow returned feature collection to specify updated --- .../service/http/tasks/AbstractApiTask.java | 7 +++--- .../http/tasks/WriteFeatureApiTask.java | 5 +++-- .../src/main/resources/static/openapi.yaml | 2 +- .../response_no_geometry.json | 3 ++- .../implementation/XyzFeatureCollection.java | 22 ++++++++++++++++++- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java index b8596637e..b6e6b914f 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java @@ -31,6 +31,7 @@ import com.here.naksha.lib.core.models.XyzError; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; +import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection.EFeatureCollectionOp; import com.here.naksha.lib.core.models.payload.XyzResponse; import com.here.naksha.lib.core.models.storage.ErrorResult; import com.here.naksha.lib.core.models.storage.ReadFeatures; @@ -135,7 +136,7 @@ protected AbstractApiTask( return verticle.sendXyzResponse( routingContext, HttpResponseType.FEATURE_COLLECTION, - new XyzFeatureCollection().withInsertedFeatures(features)); + new XyzFeatureCollection().withModifiedFeatures(features, EFeatureCollectionOp.INSERT)); } catch (NoCursor | NoSuchElementException emptyException) { logger.info("No data found in ResultCursor, returning empty collection"); return verticle.sendXyzResponse( @@ -145,7 +146,7 @@ protected AbstractApiTask( } protected @NotNull XyzResponse transformWriteResultToXyzCollectionResponse( - final @Nullable Result wrResult, final @NotNull Class type) { + final @Nullable Result wrResult, final @NotNull Class type, EFeatureCollectionOp op) { if (wrResult == null) { // unexpected null response logger.error("Received null result!"); @@ -160,7 +161,7 @@ protected AbstractApiTask( return verticle.sendXyzResponse( routingContext, HttpResponseType.FEATURE_COLLECTION, - new XyzFeatureCollection().withInsertedFeatures(features)); + new XyzFeatureCollection().withModifiedFeatures(features, op)); } catch (NoCursor | NoSuchElementException emptyException) { return verticle.sendErrorResponse( routingContext, XyzError.EXCEPTION, "Unexpected empty result from ResultCursor"); diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index a9aea54a2..f33943afb 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -27,6 +27,7 @@ import com.here.naksha.lib.core.NakshaContext; import com.here.naksha.lib.core.models.XyzError; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; +import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection.EFeatureCollectionOp; import com.here.naksha.lib.core.models.naksha.Storage; import com.here.naksha.lib.core.models.payload.XyzResponse; import com.here.naksha.lib.core.models.payload.events.QueryParameterList; @@ -128,7 +129,7 @@ protected void init() {} // Forward request to NH Space Storage writer instance final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest); // transform WriteResult to Http FeatureCollection response - return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class); + return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class, EFeatureCollectionOp.INSERT); } private @NotNull XyzResponse executeUpdateFeatures() throws Exception { @@ -164,7 +165,7 @@ protected void init() {} // Forward request to NH Space Storage writer instance final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest); // transform WriteResult to Http FeatureCollection response - return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class); + return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class, EFeatureCollectionOp.UPDATE); } private @NotNull XyzResponse executeUpdateFeature() throws Exception { diff --git a/here-naksha-app-service/src/main/resources/static/openapi.yaml b/here-naksha-app-service/src/main/resources/static/openapi.yaml index 7112165f4..79b1dfcbc 100644 --- a/here-naksha-app-service/src/main/resources/static/openapi.yaml +++ b/here-naksha-app-service/src/main/resources/static/openapi.yaml @@ -482,7 +482,7 @@ paths: summary: Update features in the space description: > This method allows to update features in the storage associated (directly or via event handler) with the space. - It ensures an atomic operation, so either all features will be created or none (in case of failure). + It ensures an atomic operation, so either all features will be updated or none (in case of failure). operationId: putFeatures parameters: - $ref: '#/components/parameters/SpaceId' diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json index 6fd7815f7..58076c52e 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json @@ -15,5 +15,6 @@ "speedLimit": "70" } } - ] + ], + "updated": ["my-custom-id-301-1","my-custom-id-301-2"] } diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java index 208696fd9..dbeb64f45 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java @@ -451,13 +451,33 @@ public XyzFeatureCollection withOldFeatures(List oldFeatures) { } @SuppressWarnings("unused") - public @NotNull XyzFeatureCollection withInsertedFeatures( + private @NotNull XyzFeatureCollection withInsertedFeatures( final @NotNull List insertedFeatures) { ((List) this.features.get()).addAll(insertedFeatures); // append features withInserted(insertedFeatures.stream().map(XyzFeature::getId).toList()); // overwrite inserted return this; } + private @NotNull XyzFeatureCollection withUpdatedFeatures( + final @NotNull List updatedFeatures) { + ((List) this.features.get()).addAll(updatedFeatures); // append features + withUpdated(updatedFeatures.stream().map(XyzFeature::getId).toList()); // overwrite inserted + return this; + } + + public @NotNull XyzFeatureCollection withModifiedFeatures( + final @NotNull List features, @NotNull EFeatureCollectionOp op) { + return switch (op) { + case INSERT -> withInsertedFeatures(features); + case UPDATE -> withUpdatedFeatures(features); + }; + } + + public enum EFeatureCollectionOp { + INSERT, + UPDATE + } + public static class ModificationFailure { private String id; From be103bee05a144f95dacef1346b0441605f7f297 Mon Sep 17 00:00:00 2001 From: phmai Date: Wed, 22 Nov 2023 15:24:21 +0100 Subject: [PATCH 10/31] revert previous change, extract features twice in transformWriteResultToXyzCollectionResponse --- .../service/http/tasks/AbstractApiTask.java | 14 +++-- .../http/tasks/WriteFeatureApiTask.java | 7 +-- .../implementation/XyzFeatureCollection.java | 19 +----- .../lib/core/util/storage/RequestHelper.java | 4 +- .../lib/core/util/storage/ResultHelper.java | 61 +++++++++++++++++++ .../naksha/lib/hub/mock/MockWriteResult.java | 9 +++ 6 files changed, 87 insertions(+), 27 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java index b6e6b914f..86be1d689 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java @@ -19,6 +19,8 @@ package com.here.naksha.app.service.http.tasks; import static com.here.naksha.lib.core.util.storage.ResultHelper.readFeaturesFromResult; +import static com.here.naksha.lib.core.util.storage.ResultHelper.readInsertedFeaturesFromResult; +import static com.here.naksha.lib.core.util.storage.ResultHelper.readUpdatedFeaturesFromResult; import static java.util.Collections.emptyList; import com.here.naksha.app.service.http.HttpResponseType; @@ -31,7 +33,6 @@ import com.here.naksha.lib.core.models.XyzError; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; -import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection.EFeatureCollectionOp; import com.here.naksha.lib.core.models.payload.XyzResponse; import com.here.naksha.lib.core.models.storage.ErrorResult; import com.here.naksha.lib.core.models.storage.ReadFeatures; @@ -136,7 +137,7 @@ protected AbstractApiTask( return verticle.sendXyzResponse( routingContext, HttpResponseType.FEATURE_COLLECTION, - new XyzFeatureCollection().withModifiedFeatures(features, EFeatureCollectionOp.INSERT)); + new XyzFeatureCollection().withInsertedFeatures(features)); } catch (NoCursor | NoSuchElementException emptyException) { logger.info("No data found in ResultCursor, returning empty collection"); return verticle.sendXyzResponse( @@ -146,7 +147,7 @@ protected AbstractApiTask( } protected @NotNull XyzResponse transformWriteResultToXyzCollectionResponse( - final @Nullable Result wrResult, final @NotNull Class type, EFeatureCollectionOp op) { + final @Nullable Result wrResult, final @NotNull Class type) { if (wrResult == null) { // unexpected null response logger.error("Received null result!"); @@ -157,11 +158,14 @@ protected AbstractApiTask( return verticle.sendErrorResponse(routingContext, er.reason, er.message); } else { try { - List features = readFeaturesFromResult(wrResult, type); + final List insertedFeatures = readInsertedFeaturesFromResult(wrResult, type, Long.MAX_VALUE); + final List updatedFeatures = readUpdatedFeaturesFromResult(wrResult, type, Long.MAX_VALUE); return verticle.sendXyzResponse( routingContext, HttpResponseType.FEATURE_COLLECTION, - new XyzFeatureCollection().withModifiedFeatures(features, op)); + new XyzFeatureCollection() + .withInsertedFeatures(insertedFeatures) + .withUpdatedFeatures(updatedFeatures)); } catch (NoCursor | NoSuchElementException emptyException) { return verticle.sendErrorResponse( routingContext, XyzError.EXCEPTION, "Unexpected empty result from ResultCursor"); diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index f33943afb..16c2792be 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -27,7 +27,6 @@ import com.here.naksha.lib.core.NakshaContext; import com.here.naksha.lib.core.models.XyzError; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; -import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection.EFeatureCollectionOp; import com.here.naksha.lib.core.models.naksha.Storage; import com.here.naksha.lib.core.models.payload.XyzResponse; import com.here.naksha.lib.core.models.payload.events.QueryParameterList; @@ -129,7 +128,7 @@ protected void init() {} // Forward request to NH Space Storage writer instance final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest); // transform WriteResult to Http FeatureCollection response - return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class, EFeatureCollectionOp.INSERT); + return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class); } private @NotNull XyzResponse executeUpdateFeatures() throws Exception { @@ -160,12 +159,12 @@ protected void init() {} feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true); } } - final WriteFeatures wrRequest = RequestHelper.updateFeaturesRequest(spaceId, features); + final WriteFeatures wrRequest = RequestHelper.upsertFeaturesRequest(spaceId, features); // Forward request to NH Space Storage writer instance final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest); // transform WriteResult to Http FeatureCollection response - return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class, EFeatureCollectionOp.UPDATE); + return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class); } private @NotNull XyzResponse executeUpdateFeature() throws Exception { diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java index dbeb64f45..1935741f9 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java @@ -451,33 +451,20 @@ public XyzFeatureCollection withOldFeatures(List oldFeatures) { } @SuppressWarnings("unused") - private @NotNull XyzFeatureCollection withInsertedFeatures( + public @NotNull XyzFeatureCollection withInsertedFeatures( final @NotNull List insertedFeatures) { ((List) this.features.get()).addAll(insertedFeatures); // append features withInserted(insertedFeatures.stream().map(XyzFeature::getId).toList()); // overwrite inserted return this; } - private @NotNull XyzFeatureCollection withUpdatedFeatures( + public @NotNull XyzFeatureCollection withUpdatedFeatures( final @NotNull List updatedFeatures) { ((List) this.features.get()).addAll(updatedFeatures); // append features - withUpdated(updatedFeatures.stream().map(XyzFeature::getId).toList()); // overwrite inserted + withUpdated(updatedFeatures.stream().map(XyzFeature::getId).toList()); // overwrite updated return this; } - public @NotNull XyzFeatureCollection withModifiedFeatures( - final @NotNull List features, @NotNull EFeatureCollectionOp op) { - return switch (op) { - case INSERT -> withInsertedFeatures(features); - case UPDATE -> withUpdatedFeatures(features); - }; - } - - public enum EFeatureCollectionOp { - INSERT, - UPDATE - } - public static class ModificationFailure { private String id; diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java index f38f358f3..dfb48bffb 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java @@ -119,11 +119,11 @@ public class RequestHelper { * @param any object extending XyzFeature * @return WriteFeatures request that can be used against IStorage methods */ - public static @NotNull WriteFeatures updateFeaturesRequest( + public static @NotNull WriteFeatures upsertFeaturesRequest( final @NotNull String collectionName, final @NotNull List features) { final List<@NotNull WriteOp> opList = new ArrayList<>(); for (final T feature : features) { - opList.add(new WriteXyzOp<>(EWriteOp.UPDATE, feature)); + opList.add(new WriteXyzOp<>(EWriteOp.PUT, feature)); } return new WriteFeatures<>(collectionName, opList); } diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java index ddddaa012..fea96aeae 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java @@ -20,6 +20,7 @@ import com.here.naksha.lib.core.exceptions.NoCursor; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; +import com.here.naksha.lib.core.models.storage.EExecutedOp; import com.here.naksha.lib.core.models.storage.Result; import com.here.naksha.lib.core.models.storage.ResultCursor; import java.util.ArrayList; @@ -92,4 +93,64 @@ public static List readFeaturesFromResult(Result resul return null; } } + + /** + * Helper method to fetch only inserted features from given Result and return list of features with type T. + * Returned list is limited with respect to supplied `limit` parameter. + * + * @param result the Result which is to be read + * @param featureType the type of feature to be extracted from result + * @param limit the max number of features to be extracted + * @param type of feature + * @return list of features extracted from ReadResult + */ + public static List readInsertedFeaturesFromResult( + Result result, Class featureType, long limit) throws NoCursor, NoSuchElementException { + try (ResultCursor resultCursor = result.cursor(featureType)) { + if (!resultCursor.hasNext()) { + throw new NoSuchElementException("Result Cursor is empty"); + } + List features = new ArrayList<>(); + int cnt = 0; + while (resultCursor.hasNext() && cnt++ < limit) { + if (!resultCursor.next()) { + throw new RuntimeException("Unexpected invalid result"); + } + if (resultCursor.getOp().equals(EExecutedOp.CREATED)) { + features.add(resultCursor.getFeature()); + } + } + return features; + } + } + + /** + * Helper method to fetch only updated features from given Result and return list of features with type T. + * Returned list is limited with respect to supplied `limit` parameter. + * + * @param result the Result which is to be read + * @param featureType the type of feature to be extracted from result + * @param limit the max number of features to be extracted + * @param type of feature + * @return list of features extracted from ReadResult + */ + public static List readUpdatedFeaturesFromResult( + Result result, Class featureType, long limit) throws NoCursor, NoSuchElementException { + try (ResultCursor resultCursor = result.cursor(featureType)) { + if (!resultCursor.hasNext()) { + throw new NoSuchElementException("Result Cursor is empty"); + } + List features = new ArrayList<>(); + int cnt = 0; + while (resultCursor.hasNext() && cnt++ < limit) { + if (!resultCursor.next()) { + throw new RuntimeException("Unexpected invalid result"); + } + if (resultCursor.getOp().equals(EExecutedOp.UPDATED)) { + features.add(resultCursor.getFeature()); + } + } + return features; + } + } } diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/MockWriteResult.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/MockWriteResult.java index cf85550ab..6ffe1cae4 100644 --- a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/MockWriteResult.java +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/MockWriteResult.java @@ -19,6 +19,7 @@ package com.here.naksha.lib.hub.mock; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; +import com.here.naksha.lib.core.models.storage.EExecutedOp; import com.here.naksha.lib.core.models.storage.SuccessResult; import com.here.naksha.lib.core.models.storage.WriteOpResult; import java.util.List; @@ -50,5 +51,13 @@ class WriteOpResultCursor extends MockResultCursor { } return writeOpResults.get(currentPos).feature; } + + @Override + public @NotNull EExecutedOp getOp() throws NoSuchElementException { + if (!isPositionValid()) { + throw new NoSuchElementException(); + } + return writeOpResults.get(currentPos).op; + } } } From e2051171aad69903dbbca69f0b5e58a565d771cd Mon Sep 17 00:00:00 2001 From: phmai Date: Wed, 22 Nov 2023 16:20:24 +0100 Subject: [PATCH 11/31] adjust result helper to not rely on cursor reposition --- .../service/http/tasks/AbstractApiTask.java | 16 +++--- .../lib/core/util/storage/ResultHelper.java | 51 ++++++++----------- 2 files changed, 27 insertions(+), 40 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java index 4c0071a20..6e97a18e3 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java @@ -18,9 +18,7 @@ */ package com.here.naksha.app.service.http.tasks; -import static com.here.naksha.lib.core.util.storage.ResultHelper.readFeaturesFromResult; -import static com.here.naksha.lib.core.util.storage.ResultHelper.readInsertedFeaturesFromResult; -import static com.here.naksha.lib.core.util.storage.ResultHelper.readUpdatedFeaturesFromResult; +import static com.here.naksha.lib.core.util.storage.ResultHelper.*; import static java.util.Collections.emptyList; import com.here.naksha.app.service.http.HttpResponseType; @@ -34,14 +32,12 @@ import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; import com.here.naksha.lib.core.models.payload.XyzResponse; -import com.here.naksha.lib.core.models.storage.ErrorResult; -import com.here.naksha.lib.core.models.storage.ReadFeatures; -import com.here.naksha.lib.core.models.storage.Result; -import com.here.naksha.lib.core.models.storage.WriteFeatures; +import com.here.naksha.lib.core.models.storage.*; import com.here.naksha.lib.core.storage.IReadSession; import com.here.naksha.lib.core.storage.IWriteSession; import io.vertx.ext.web.RoutingContext; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -164,8 +160,10 @@ protected AbstractApiTask( return verticle.sendErrorResponse(routingContext, er.reason, er.message); } else { try { - final List insertedFeatures = readInsertedFeaturesFromResult(wrResult, type, Long.MAX_VALUE); - final List updatedFeatures = readUpdatedFeaturesFromResult(wrResult, type, Long.MAX_VALUE); + final Map> featureMap = readFeaturesGroupedByOp(wrResult, type); + final List insertedFeatures = featureMap.get(EExecutedOp.CREATED); + final List updatedFeatures = featureMap.get(EExecutedOp.UPDATED); + //TODO later DELETED might be needed return verticle.sendXyzResponse( routingContext, HttpResponseType.FEATURE_COLLECTION, diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java index fea96aeae..2f0ac12db 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java @@ -23,9 +23,7 @@ import com.here.naksha.lib.core.models.storage.EExecutedOp; import com.here.naksha.lib.core.models.storage.Result; import com.here.naksha.lib.core.models.storage.ResultCursor; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; +import java.util.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -95,62 +93,53 @@ public static List readFeaturesFromResult(Result resul } /** - * Helper method to fetch only inserted features from given Result and return list of features with type T. - * Returned list is limited with respect to supplied `limit` parameter. + * Helper method to fetch features from given Result and return a map of multiple lists grouped by {@link EExecutedOp} of features with type T. + * Returned lists are limited with respect to supplied `limit` parameter. * * @param result the Result which is to be read * @param featureType the type of feature to be extracted from result * @param limit the max number of features to be extracted * @param type of feature - * @return list of features extracted from ReadResult + * @return a map grouping the lists of features extracted from ReadResult */ - public static List readInsertedFeaturesFromResult( + public static Map> readFeaturesGroupedByOp( Result result, Class featureType, long limit) throws NoCursor, NoSuchElementException { try (ResultCursor resultCursor = result.cursor(featureType)) { if (!resultCursor.hasNext()) { throw new NoSuchElementException("Result Cursor is empty"); } - List features = new ArrayList<>(); + final List insertedFeatures = new ArrayList<>(); + final List updatedFeatures = new ArrayList<>(); int cnt = 0; while (resultCursor.hasNext() && cnt++ < limit) { if (!resultCursor.next()) { throw new RuntimeException("Unexpected invalid result"); } if (resultCursor.getOp().equals(EExecutedOp.CREATED)) { - features.add(resultCursor.getFeature()); + insertedFeatures.add(resultCursor.getFeature()); + } else if (resultCursor.getOp().equals(EExecutedOp.UPDATED)) { + updatedFeatures.add(resultCursor.getFeature()); } } + final Map> features = new HashMap<>(); + features.put(EExecutedOp.CREATED, insertedFeatures); + features.put(EExecutedOp.UPDATED, updatedFeatures); + // TODO add other lists for DELETED,... return features; } } /** - * Helper method to fetch only updated features from given Result and return list of features with type T. - * Returned list is limited with respect to supplied `limit` parameter. + * Helper method to fetch features from given Result and return a map of multiple lists grouped by {@link EExecutedOp} of features with type T. + * Returned list is not limited - to set the upper bound, use sibling method with limit argument. * * @param result the Result which is to be read * @param featureType the type of feature to be extracted from result - * @param limit the max number of features to be extracted * @param type of feature - * @return list of features extracted from ReadResult + * @return a map grouping the lists of features extracted from ReadResult */ - public static List readUpdatedFeaturesFromResult( - Result result, Class featureType, long limit) throws NoCursor, NoSuchElementException { - try (ResultCursor resultCursor = result.cursor(featureType)) { - if (!resultCursor.hasNext()) { - throw new NoSuchElementException("Result Cursor is empty"); - } - List features = new ArrayList<>(); - int cnt = 0; - while (resultCursor.hasNext() && cnt++ < limit) { - if (!resultCursor.next()) { - throw new RuntimeException("Unexpected invalid result"); - } - if (resultCursor.getOp().equals(EExecutedOp.UPDATED)) { - features.add(resultCursor.getFeature()); - } - } - return features; - } + public static Map> readFeaturesGroupedByOp( + Result result, Class featureType) throws NoCursor, NoSuchElementException { + return readFeaturesGroupedByOp(result, featureType, Long.MAX_VALUE); } } From 07b3ab9e5e2a399f7156ccbeed5bb5d93e5b029c Mon Sep 17 00:00:00 2001 From: phmai Date: Wed, 22 Nov 2023 17:05:22 +0100 Subject: [PATCH 12/31] switch to test helper class --- .../naksha/app/service/NakshaAppTest.java | 88 +------------ .../app/service/UpdateFeatureTestHelper.java | 122 ++++++++++++++++++ 2 files changed, 127 insertions(+), 83 deletions(-) create mode 100644 here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index b5a2ed057..0a5ac3348 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -43,6 +43,7 @@ class NakshaAppTest { static CreateFeatureTestHelper createFeatureTests; static ReadFeaturesByIdsTestHelper readFeaturesByIdsTests; + static UpdateFeatureTestHelper updateFeatureTestHelper; @BeforeAll static void prepare() throws InterruptedException, URISyntaxException { @@ -667,104 +668,25 @@ void tc0407_testReadFeaturesWithCommaSeparatedIds() throws Exception { @Test @Order(10) void tc0500_testUpdateFeatures() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - // Read request body - final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + updateFeatureTestHelper.tc0500_testUpdateFeatures(); } @Test @Order(11) void tc0501_testUpdateFeatureById() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); - final String expectedBodyPart = bodyJson; - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + updateFeatureTestHelper.tc0501_testUpdateFeatureById(); } @Test @Order(11) void tc0502_testUpdateFeatureByWrongUriId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(409, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + updateFeatureTestHelper.tc0502_testUpdateFeatureByWrongUriId(); } @Test @Order(11) void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(400, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + updateFeatureTestHelper.tc0503_testUpdateFeatureWithMismatchingId(); } @AfterAll diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java new file mode 100644 index 000000000..f77248000 --- /dev/null +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -0,0 +1,122 @@ +package com.here.naksha.app.service; + +import com.here.naksha.app.common.NakshaTestWebClient; +import com.here.naksha.lib.core.models.naksha.Space; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +import java.net.http.HttpResponse; +import java.util.UUID; + +import static com.here.naksha.app.common.TestUtil.*; +import static com.here.naksha.app.common.TestUtil.HDR_STREAM_ID; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UpdateFeatureTestHelper { + + final @NotNull NakshaApp app; + final @NotNull NakshaTestWebClient nakshaClient; + + public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { + this.app = app; + this.nakshaClient = nakshaClient; + } + + void tc0500_testUpdateFeatures() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + // Read request body + final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0501_testUpdateFeatureById() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); + final String expectedBodyPart = bodyJson; + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0502_testUpdateFeatureByWrongUriId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(409, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(400, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } +} From d1499c1b56991fab7cc3c8d3b3b08bcd80319eb7 Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 23 Nov 2023 10:04:30 +0100 Subject: [PATCH 13/31] add deleted features readout ability --- .../naksha/app/service/http/tasks/AbstractApiTask.java | 8 +++++--- .../geojson/implementation/XyzFeatureCollection.java | 7 +++++++ .../here/naksha/lib/core/util/storage/ResultHelper.java | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java index 6e97a18e3..c157e3fac 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java @@ -18,7 +18,8 @@ */ package com.here.naksha.app.service.http.tasks; -import static com.here.naksha.lib.core.util.storage.ResultHelper.*; +import static com.here.naksha.lib.core.util.storage.ResultHelper.readFeaturesGroupedByOp; +import static com.here.naksha.lib.core.util.storage.ResultHelper.readFeaturesFromResult; import static java.util.Collections.emptyList; import com.here.naksha.app.service.http.HttpResponseType; @@ -163,13 +164,14 @@ protected AbstractApiTask( final Map> featureMap = readFeaturesGroupedByOp(wrResult, type); final List insertedFeatures = featureMap.get(EExecutedOp.CREATED); final List updatedFeatures = featureMap.get(EExecutedOp.UPDATED); - //TODO later DELETED might be needed + final List deletedFeatures = featureMap.get(EExecutedOp.DELETED); return verticle.sendXyzResponse( routingContext, HttpResponseType.FEATURE_COLLECTION, new XyzFeatureCollection() .withInsertedFeatures(insertedFeatures) - .withUpdatedFeatures(updatedFeatures)); + .withUpdatedFeatures(updatedFeatures) + .withDeletedFeatures(deletedFeatures)); } catch (NoCursor | NoSuchElementException emptyException) { return verticle.sendErrorResponse( routingContext, XyzError.EXCEPTION, "Unexpected empty result from ResultCursor"); diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java index 1935741f9..ea219b854 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java @@ -465,6 +465,13 @@ public XyzFeatureCollection withOldFeatures(List oldFeatures) { return this; } + public @NotNull XyzFeatureCollection withDeletedFeatures( + final @NotNull List deletedFeatures) { + ((List) this.features.get()).addAll(deletedFeatures); // append features + withDeleted(deletedFeatures.stream().map(XyzFeature::getId).toList()); // overwrite deleted + return this; + } + public static class ModificationFailure { private String id; diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java index 2f0ac12db..20fe1ab96 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java @@ -110,6 +110,7 @@ public static Map> readFeaturesGroup } final List insertedFeatures = new ArrayList<>(); final List updatedFeatures = new ArrayList<>(); + final List deletedFeatures = new ArrayList<>(); int cnt = 0; while (resultCursor.hasNext() && cnt++ < limit) { if (!resultCursor.next()) { @@ -119,12 +120,14 @@ public static Map> readFeaturesGroup insertedFeatures.add(resultCursor.getFeature()); } else if (resultCursor.getOp().equals(EExecutedOp.UPDATED)) { updatedFeatures.add(resultCursor.getFeature()); + } else if (resultCursor.getOp().equals(EExecutedOp.DELETED)) { + deletedFeatures.add(resultCursor.getFeature()); } } final Map> features = new HashMap<>(); features.put(EExecutedOp.CREATED, insertedFeatures); features.put(EExecutedOp.UPDATED, updatedFeatures); - // TODO add other lists for DELETED,... + features.put(EExecutedOp.DELETED, deletedFeatures); return features; } } From 61d8c57a6a1254541c89b0f8ba0d086e793f43e1 Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 23 Nov 2023 10:37:59 +0100 Subject: [PATCH 14/31] decouple update feature test resource from create --- .../service/http/tasks/AbstractApiTask.java | 4 +- .../naksha/app/service/NakshaAppTest.java | 2 +- .../app/service/UpdateFeatureTestHelper.java | 245 ++++++++++-------- .../create_features.json | 27 ++ .../TC0500_updateFeatures/create_handler.json | 12 + .../TC0500_updateFeatures/create_space.json | 15 ++ .../TC0500_updateFeatures/create_storage.json | 7 + .../implementation/XyzFeatureCollection.java | 2 +- 8 files changed, 199 insertions(+), 115 deletions(-) create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_features.json create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_handler.json create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_space.json create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_storage.json diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java index c157e3fac..73af6627b 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/AbstractApiTask.java @@ -18,8 +18,8 @@ */ package com.here.naksha.app.service.http.tasks; -import static com.here.naksha.lib.core.util.storage.ResultHelper.readFeaturesGroupedByOp; import static com.here.naksha.lib.core.util.storage.ResultHelper.readFeaturesFromResult; +import static com.here.naksha.lib.core.util.storage.ResultHelper.readFeaturesGroupedByOp; import static java.util.Collections.emptyList; import com.here.naksha.app.service.http.HttpResponseType; @@ -171,7 +171,7 @@ protected AbstractApiTask( new XyzFeatureCollection() .withInsertedFeatures(insertedFeatures) .withUpdatedFeatures(updatedFeatures) - .withDeletedFeatures(deletedFeatures)); + .withDeletedFeatures(deletedFeatures)); } catch (NoCursor | NoSuchElementException emptyException) { return verticle.sendErrorResponse( routingContext, XyzError.EXCEPTION, "Unexpected empty result from ResultCursor"); diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index 0a5ac3348..f525a0ab4 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.here.naksha.app.common.NakshaTestWebClient; -import com.here.naksha.lib.core.models.naksha.Space; import com.here.naksha.lib.hub.NakshaHubConfig; import com.here.naksha.lib.psql.PsqlStorage; import java.net.URISyntaxException; @@ -57,6 +56,7 @@ static void prepare() throws InterruptedException, URISyntaxException { // create test helpers createFeatureTests = new CreateFeatureTestHelper(app, nakshaClient); readFeaturesByIdsTests = new ReadFeaturesByIdsTestHelper(app, nakshaClient); + updateFeatureTestHelper = new UpdateFeatureTestHelper(app, nakshaClient); } @Test diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index f77248000..b8df0f4ad 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -1,122 +1,145 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ package com.here.naksha.app.service; +import static com.here.naksha.app.common.TestUtil.*; +import static com.here.naksha.app.common.TestUtil.HDR_STREAM_ID; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.here.naksha.app.common.NakshaTestWebClient; import com.here.naksha.lib.core.models.naksha.Space; +import java.net.http.HttpResponse; +import java.util.UUID; import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; -import java.net.http.HttpResponse; -import java.util.UUID; - -import static com.here.naksha.app.common.TestUtil.*; -import static com.here.naksha.app.common.TestUtil.HDR_STREAM_ID; -import static org.junit.jupiter.api.Assertions.assertEquals; - public class UpdateFeatureTestHelper { - final @NotNull NakshaApp app; - final @NotNull NakshaTestWebClient nakshaClient; - - public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { - this.app = app; - this.nakshaClient = nakshaClient; - } - - void tc0500_testUpdateFeatures() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - // Read request body - final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0501_testUpdateFeatureById() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); - final String expectedBodyPart = bodyJson; - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0502_testUpdateFeatureByWrongUriId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(409, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(400, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } + final @NotNull NakshaApp app; + final @NotNull NakshaTestWebClient nakshaClient; + + public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { + this.app = app; + this.nakshaClient = nakshaClient; + } + + void tc0500_testUpdateFeatures() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Preparation: create storage, event handler and space + final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); + nakshaClient.post("hub/storages", storage, streamId); + final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); + nakshaClient.post("hub/handlers", handler, streamId); + final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); + nakshaClient.post("hub/spaces", spaceJson, streamId); + // Read request body + final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0501_testUpdateFeatureById() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = bodyJson; + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0502_testUpdateFeatureByWrongUriId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(409, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(400, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_features.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_features.json new file mode 100644 index 000000000..865315c72 --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_features.json @@ -0,0 +1,27 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "id": "my-custom-id-301-1", + "type": "Feature", + "properties": { + "speedLimit": "60" + }, + "geometry": { + "type":"Point", + "coordinates":[ 8.68872, 50.0561, 292.94377758] + } + }, + { + "id": "my-custom-id-301-2", + "type": "Feature", + "properties": { + "speedLimit": "80" + }, + "geometry": { + "type":"Point", + "coordinates":[ 9.68872, 51.0561, 293.94377758] + } + } + ] +} diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_handler.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_handler.json new file mode 100644 index 000000000..afb979955 --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_handler.json @@ -0,0 +1,12 @@ +{ + "id": "feature-update-handler", + "type": "EventHandler", + "title": "Storage Handler for UniMap Moderation Dev Storage", + "description": "Default Naksha Storage Handler for operations on UniMap Moderation Dev Storage", + "className": "com.here.naksha.lib.handlers.DefaultStorageHandler", + "active": true, + "extensionId": null, + "properties": { + "storageId": "local-mock-for-updating-feature" + } +} \ No newline at end of file diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_space.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_space.json new file mode 100644 index 000000000..01c04ef47 --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_space.json @@ -0,0 +1,15 @@ +{ + "id": "feature-update-space", + "type": "Space", + "title": "Topology Space for UniMap Moderation Dev Storage", + "description": "Space for managing Topology Feature collection in UniMap Moderation Dev Storage", + "eventHandlerIds": [ + "feature-update-handler" + ], + "properties": { + "storageCollection": { + "id": "um-mod-dev:topology", + "type": "StorageCollection" + } + } +} \ No newline at end of file diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_storage.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_storage.json new file mode 100644 index 000000000..e5b187413 --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_storage.json @@ -0,0 +1,7 @@ +{ + "id": "local-mock-for-updating-feature", + "type": "Storage", + "title": "Local Mock Storage", + "description": "Local InMemory Storage implementation mocked for testing purpose", + "className": "com.here.naksha.lib.hub.mock.NHAdminMock" +} \ No newline at end of file diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java index ea219b854..36eaafc6d 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/implementation/XyzFeatureCollection.java @@ -466,7 +466,7 @@ public XyzFeatureCollection withOldFeatures(List oldFeatures) { } public @NotNull XyzFeatureCollection withDeletedFeatures( - final @NotNull List deletedFeatures) { + final @NotNull List deletedFeatures) { ((List) this.features.get()).addAll(deletedFeatures); // append features withDeleted(deletedFeatures.stream().map(XyzFeature::getId).toList()); // overwrite deleted return this; From e6c8c62bf45e0a3b0005bd070c029002c1bb4c89 Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 23 Nov 2023 14:28:15 +0100 Subject: [PATCH 15/31] add test --- .../naksha/app/service/NakshaAppTest.java | 6 +++++ .../app/service/UpdateFeatureTestHelper.java | 26 ++++++++++++++++++- .../response_no_geometry.json | 10 ++++++- .../TC0500_updateFeatures/update_request.json | 11 ++++++++ .../TC0504_updateFeaturesNoIds/request.json | 11 ++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 here-naksha-app-service/src/test/resources/unit_test_data/TC0504_updateFeaturesNoIds/request.json diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index f525a0ab4..6507a817e 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -689,6 +689,12 @@ void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { updateFeatureTestHelper.tc0503_testUpdateFeatureWithMismatchingId(); } + @Test + @Order(11) + void tc0504_testUpdateFeaturesNoId() throws Exception { + updateFeatureTestHelper.tc0504_testUpdateFeaturesNoId(); + } + @AfterAll static void close() throws InterruptedException { if (app != null) { diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index b8df0f4ad..2835ffb0c 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -19,14 +19,16 @@ package com.here.naksha.app.service; import static com.here.naksha.app.common.TestUtil.*; -import static com.here.naksha.app.common.TestUtil.HDR_STREAM_ID; import static org.junit.jupiter.api.Assertions.assertEquals; import com.here.naksha.app.common.NakshaTestWebClient; +import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; import com.here.naksha.lib.core.models.naksha.Space; import java.net.http.HttpResponse; +import java.util.List; import java.util.UUID; import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; @@ -142,4 +144,26 @@ void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { JSONCompareMode.LENIENT); assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); } + + void tc0504_testUpdateFeaturesNoId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Read request body + final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + + final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final List inserted = responseFeatureCollection.getInserted(); + Assertions.assertEquals(1, inserted.size()); + } } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json index 58076c52e..3b4d3dfae 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json @@ -14,7 +14,15 @@ "properties": { "speedLimit": "70" } + }, + { + "id": "newly-inserted", + "type": "Feature", + "properties": { + "isImportant": "no" + } } ], - "updated": ["my-custom-id-301-1","my-custom-id-301-2"] + "updated": ["my-custom-id-301-1","my-custom-id-301-2"], + "inserted": ["newly-inserted"] } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json index 6fd7815f7..fcea91e73 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json @@ -14,6 +14,17 @@ "properties": { "speedLimit": "70" } + }, + { + "id": "newly-inserted", + "type": "Feature", + "properties": { + "isImportant": "no" + }, + "geometry": { + "type":"Point", + "coordinates":[ 5, 10, 15] + } } ] } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0504_updateFeaturesNoIds/request.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0504_updateFeaturesNoIds/request.json new file mode 100644 index 000000000..7af379b88 --- /dev/null +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0504_updateFeaturesNoIds/request.json @@ -0,0 +1,11 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "speedLimit": "50" + } + } + ] +} From a212dcb233f47f167793703dfe08bead7a3c9f0a Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 23 Nov 2023 15:43:45 +0100 Subject: [PATCH 16/31] add test --- .../naksha/app/service/NakshaAppTest.java | 6 + .../app/service/UpdateFeatureTestHelper.java | 348 +++++++++++------- .../TC0504_updateFeaturesNoIds/request.json | 7 + 3 files changed, 229 insertions(+), 132 deletions(-) diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index 6507a817e..9f1744270 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -695,6 +695,12 @@ void tc0504_testUpdateFeaturesNoId() throws Exception { updateFeatureTestHelper.tc0504_testUpdateFeaturesNoId(); } + @Test + @Order(12) + void tc0505_testUpdateFeaturesWithUuid() throws Exception { + updateFeatureTestHelper.tc0505_testUpdateFeaturesWithUuid(); + } + @AfterAll static void close() throws InterruptedException { if (app != null) { diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index 2835ffb0c..9ccdaf44d 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.here.naksha.app.common.NakshaTestWebClient; +import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; +import com.here.naksha.lib.core.models.geojson.implementation.XyzProperties; import com.here.naksha.lib.core.models.naksha.Space; + import java.net.http.HttpResponse; import java.util.List; import java.util.UUID; + import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.skyscreamer.jsonassert.JSONAssert; @@ -34,136 +38,216 @@ public class UpdateFeatureTestHelper { - final @NotNull NakshaApp app; - final @NotNull NakshaTestWebClient nakshaClient; - - public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { - this.app = app; - this.nakshaClient = nakshaClient; - } - - void tc0500_testUpdateFeatures() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Preparation: create storage, event handler and space - final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); - nakshaClient.post("hub/storages", storage, streamId); - final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); - nakshaClient.post("hub/handlers", handler, streamId); - final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); - nakshaClient.post("hub/spaces", spaceJson, streamId); - // Read request body - final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0501_testUpdateFeatureById() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = bodyJson; - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0502_testUpdateFeatureByWrongUriId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(409, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(400, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0504_testUpdateFeaturesNoId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Read request body - final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - - final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(responseFeatureCollection); - final List inserted = responseFeatureCollection.getInserted(); - Assertions.assertEquals(1, inserted.size()); - } + final @NotNull NakshaApp app; + final @NotNull NakshaTestWebClient nakshaClient; + + public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { + this.app = app; + this.nakshaClient = nakshaClient; + } + + void tc0500_testUpdateFeatures() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Preparation: create storage, event handler and space + final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); + nakshaClient.post("hub/storages", storage, streamId); + final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); + nakshaClient.post("hub/handlers", handler, streamId); + final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); + nakshaClient.post("hub/spaces", spaceJson, streamId); + // Read request body + final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0501_testUpdateFeatureById() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = bodyJson; + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0502_testUpdateFeatureByWrongUriId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(409, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(400, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0504_testUpdateFeaturesNoId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Read request body + final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + + final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final List inserted = responseFeatureCollection.getInserted(); + Assertions.assertEquals(2, inserted.size()); + } + + void tc0505_testUpdateFeaturesWithUuid() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + // When: Create Features request is submitted to NakshaHub Space Storage instance + + final HttpResponse getResponse = + nakshaClient.get("hub/spaces/" + space.getId() + "/features/my-custom-id-301-2", streamId); + final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); + Assertions.assertNotNull(feature); + final XyzProperties newPropsOldUuid = feature.getProperties(); + final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); + final XyzProperties nullUuidProps = new XyzProperties(); + // Old UUID + newPropsOldUuid.put("speedLimit", "30"); + // New UUID + newPropsOutdatedUuid.put("speedLimit", "120"); + // Null UUID + nullUuidProps.put("uuid", null); + nullUuidProps.put("overriden", "yesyesyes"); + + // Execute request, correct UUID, should success + feature.setProperties(newPropsOldUuid); + final HttpResponse responseUpdateSuccess = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ + { + "type": "FeatureCollection", + "features": [ + """ + feature + "]}", + streamId); + + // Perform first assertions + assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); + final XyzFeatureCollection responseFeatureCollection = + parseJson(responseUpdateSuccess.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final XyzFeature updatedFeature = + responseFeatureCollection.getFeatures().get(0); + Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); + + // Execute request, outdated UUID, should fail + feature.setProperties(newPropsOutdatedUuid); + final HttpResponse responseUpdateFail = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ + { + "type": "FeatureCollection", + "features": [ + """ + feature + "]}", + streamId); + + // Perform second assertions + assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); + + // Execute request, null UUID, should success with overriding + feature.setProperties(nullUuidProps); + final HttpResponse responseOverriding = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ + { + "type": "FeatureCollection", + "features": [ + """ + feature + "]}", + streamId); + + // Perform third assertions + assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); + final XyzFeatureCollection featureCollection = parseJson(responseOverriding.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(featureCollection); + final XyzFeature overridenFeature = featureCollection.getFeatures().get(0); + Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); + // Old properties like speedLimit should no longer be available + // The feature has been completely overwritten by the PUT request with null UUID + Assertions.assertFalse(overridenFeature.getProperties().containsKey("speedLimit")); + } } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0504_updateFeaturesNoIds/request.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0504_updateFeaturesNoIds/request.json index 7af379b88..8aa854b08 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0504_updateFeaturesNoIds/request.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0504_updateFeaturesNoIds/request.json @@ -6,6 +6,13 @@ "properties": { "speedLimit": "50" } + }, + { + "type": "Feature", + "properties": { + "speedLimit": "100", + "uuid": "whachamacallit" + } } ] } From 62ed72e53f6a5a29790a011e105b0e750da829ba Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 23 Nov 2023 16:00:28 +0100 Subject: [PATCH 17/31] add test --- .../naksha/app/service/NakshaAppTest.java | 6 + .../app/service/UpdateFeatureTestHelper.java | 481 ++++++++++-------- 2 files changed, 273 insertions(+), 214 deletions(-) diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index 9f1744270..2b4988158 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -701,6 +701,12 @@ void tc0505_testUpdateFeaturesWithUuid() throws Exception { updateFeatureTestHelper.tc0505_testUpdateFeaturesWithUuid(); } + @Test + @Order(12) + void tc0506_testUpdateFeatureWithUuid() throws Exception { + updateFeatureTestHelper.tc0506_testUpdateFeatureWithUuid(); + } + @AfterAll static void close() throws InterruptedException { if (app != null) { diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index 9ccdaf44d..a04c8bfb6 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -26,11 +26,9 @@ import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; import com.here.naksha.lib.core.models.geojson.implementation.XyzProperties; import com.here.naksha.lib.core.models.naksha.Space; - import java.net.http.HttpResponse; import java.util.List; import java.util.UUID; - import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.skyscreamer.jsonassert.JSONAssert; @@ -38,216 +36,271 @@ public class UpdateFeatureTestHelper { - final @NotNull NakshaApp app; - final @NotNull NakshaTestWebClient nakshaClient; - - public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { - this.app = app; - this.nakshaClient = nakshaClient; - } - - void tc0500_testUpdateFeatures() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Preparation: create storage, event handler and space - final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); - nakshaClient.post("hub/storages", storage, streamId); - final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); - nakshaClient.post("hub/handlers", handler, streamId); - final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); - nakshaClient.post("hub/spaces", spaceJson, streamId); - // Read request body - final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0501_testUpdateFeatureById() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = bodyJson; - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0502_testUpdateFeatureByWrongUriId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(409, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(400, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0504_testUpdateFeaturesNoId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Read request body - final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - - final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(responseFeatureCollection); - final List inserted = responseFeatureCollection.getInserted(); - Assertions.assertEquals(2, inserted.size()); - } - - void tc0505_testUpdateFeaturesWithUuid() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - // When: Create Features request is submitted to NakshaHub Space Storage instance - - final HttpResponse getResponse = - nakshaClient.get("hub/spaces/" + space.getId() + "/features/my-custom-id-301-2", streamId); - final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); - Assertions.assertNotNull(feature); - final XyzProperties newPropsOldUuid = feature.getProperties(); - final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); - final XyzProperties nullUuidProps = new XyzProperties(); - // Old UUID - newPropsOldUuid.put("speedLimit", "30"); - // New UUID - newPropsOutdatedUuid.put("speedLimit", "120"); - // Null UUID - nullUuidProps.put("uuid", null); - nullUuidProps.put("overriden", "yesyesyes"); - - // Execute request, correct UUID, should success - feature.setProperties(newPropsOldUuid); - final HttpResponse responseUpdateSuccess = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ - { - "type": "FeatureCollection", - "features": [ - """ + feature + "]}", - streamId); - - // Perform first assertions - assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); - final XyzFeatureCollection responseFeatureCollection = - parseJson(responseUpdateSuccess.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(responseFeatureCollection); - final XyzFeature updatedFeature = - responseFeatureCollection.getFeatures().get(0); - Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); - - // Execute request, outdated UUID, should fail - feature.setProperties(newPropsOutdatedUuid); - final HttpResponse responseUpdateFail = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ - { - "type": "FeatureCollection", - "features": [ - """ + feature + "]}", - streamId); - - // Perform second assertions - assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); - - // Execute request, null UUID, should success with overriding - feature.setProperties(nullUuidProps); - final HttpResponse responseOverriding = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ - { - "type": "FeatureCollection", - "features": [ - """ + feature + "]}", - streamId); - - // Perform third assertions - assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); - final XyzFeatureCollection featureCollection = parseJson(responseOverriding.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(featureCollection); - final XyzFeature overridenFeature = featureCollection.getFeatures().get(0); - Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); - // Old properties like speedLimit should no longer be available - // The feature has been completely overwritten by the PUT request with null UUID - Assertions.assertFalse(overridenFeature.getProperties().containsKey("speedLimit")); - } + final @NotNull NakshaApp app; + final @NotNull NakshaTestWebClient nakshaClient; + + public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { + this.app = app; + this.nakshaClient = nakshaClient; + } + + void tc0500_testUpdateFeatures() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Preparation: create storage, event handler and space + final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); + nakshaClient.post("hub/storages", storage, streamId); + final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); + nakshaClient.post("hub/handlers", handler, streamId); + final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); + nakshaClient.post("hub/spaces", spaceJson, streamId); + // Read request body + final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0501_testUpdateFeatureById() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = bodyJson; + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0502_testUpdateFeatureByWrongUriId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(409, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(400, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0504_testUpdateFeaturesNoId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Read request body + final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + + final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final List inserted = responseFeatureCollection.getInserted(); + Assertions.assertEquals(2, inserted.size()); + } + + void tc0505_testUpdateFeaturesWithUuid() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + + final HttpResponse getResponse = + nakshaClient.get("hub/spaces/" + space.getId() + "/features/my-custom-id-301-2", streamId); + final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); + Assertions.assertNotNull(feature); + final XyzProperties newPropsOldUuid = feature.getProperties(); + final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); + final XyzProperties nullUuidProps = new XyzProperties(); + // Old UUID + newPropsOldUuid.put("speedLimit", "30"); + // New UUID + newPropsOutdatedUuid.put("speedLimit", "120"); + // Null UUID + nullUuidProps.put("uuid", null); + nullUuidProps.put("overriden", "yesyesyes"); + + // Execute request, correct UUID, should success + feature.setProperties(newPropsOldUuid); + final HttpResponse responseUpdateSuccess = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ +{ +"type": "FeatureCollection", +"features": [ +""" + feature + "]}", + streamId); + + // Perform first assertions + assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); + final XyzFeatureCollection responseFeatureCollection = + parseJson(responseUpdateSuccess.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final XyzFeature updatedFeature = + responseFeatureCollection.getFeatures().get(0); + Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); + + // Execute request, outdated UUID, should fail + feature.setProperties(newPropsOutdatedUuid); + final HttpResponse responseUpdateFail = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ +{ +"type": "FeatureCollection", +"features": [ +""" + feature + "]}", + streamId); + + // Perform second assertions + assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); + + // Execute request, null UUID, should success with overriding + feature.setProperties(nullUuidProps); + final HttpResponse responseOverriding = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ +{ +"type": "FeatureCollection", +"features": [ +""" + feature + "]}", + streamId); + + // Perform third assertions + assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); + final XyzFeatureCollection featureCollection = parseJson(responseOverriding.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(featureCollection); + final XyzFeature overridenFeature = featureCollection.getFeatures().get(0); + Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); + // Old properties like speedLimit should no longer be available + // The feature has been completely overwritten by the PUT request with null UUID + Assertions.assertFalse(overridenFeature.getProperties().containsKey("speedLimit")); + } + + void tc0506_testUpdateFeatureWithUuid() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + final String streamId = UUID.randomUUID().toString(); + + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + + final HttpResponse getResponse = + nakshaClient.get("hub/spaces/" + space.getId() + "/features/newly-inserted", streamId); + final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); + Assertions.assertNotNull(feature); + final XyzProperties newPropsOldUuid = feature.getProperties(); + final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); + final XyzProperties nullUuidProps = new XyzProperties(); + // Old UUID + newPropsOldUuid.put("speedLimit", "30"); + // New UUID + newPropsOutdatedUuid.put("speedLimit", "120"); + // Null UUID + nullUuidProps.put("uuid", null); + nullUuidProps.put("overriden", "yesyesyes"); + + // Execute request, correct UUID, should success + feature.setProperties(newPropsOldUuid); + final HttpResponse responseUpdateSuccess = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform first assertions + assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); + final XyzFeature updatedFeature = parseJson(responseUpdateSuccess.body(), XyzFeature.class); + Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); + Assertions.assertEquals("no", updatedFeature.getProperties().get("isImportant")); + + // Execute request, outdated UUID, should fail + feature.setProperties(newPropsOutdatedUuid); + final HttpResponse responseUpdateFail = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform second assertions + assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); + + // Execute request, null UUID, should success with overriding + feature.setProperties(nullUuidProps); + final HttpResponse responseOverriding = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform third assertions + assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); + final XyzFeature overridenFeature = parseJson(responseOverriding.body(), XyzFeature.class); + Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); + // Old properties like isImportant should no longer be available + // The feature has been completely overwritten by the PUT request with null UUID + Assertions.assertFalse(overridenFeature.getProperties().containsKey("isImportant")); + } } From cf1eb0566176e83bf0fca10e25e3379d9483247f Mon Sep 17 00:00:00 2001 From: phmai Date: Fri, 24 Nov 2023 15:07:24 +0100 Subject: [PATCH 18/31] explicit imports --- .../naksha/app/service/http/tasks/WriteFeatureApiTask.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index 16c2792be..565fb5672 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -18,7 +18,12 @@ */ package com.here.naksha.app.service.http.tasks; -import static com.here.naksha.app.service.http.apis.ApiParams.*; +import static com.here.naksha.app.service.http.apis.ApiParams.FEATURE_ID; +import static com.here.naksha.app.service.http.apis.ApiParams.SPACE_ID; +import static com.here.naksha.app.service.http.apis.ApiParams.ADD_TAGS; +import static com.here.naksha.app.service.http.apis.ApiParams.REMOVE_TAGS; +import static com.here.naksha.app.service.http.apis.ApiParams.PREFIX_ID; +import static com.here.naksha.app.service.http.apis.ApiParams.pathParam; import com.fasterxml.jackson.core.JsonProcessingException; import com.here.naksha.app.service.http.NakshaHttpVerticle; From b51b3c7d7994930303d0227f96d63d03540453d1 Mon Sep 17 00:00:00 2001 From: phmai Date: Fri, 24 Nov 2023 15:07:38 +0100 Subject: [PATCH 19/31] autoformat --- .../app/service/UpdateFeatureTestHelper.java | 536 +++++++++--------- 1 file changed, 269 insertions(+), 267 deletions(-) diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index a04c8bfb6..bef469ec7 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -26,9 +26,11 @@ import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; import com.here.naksha.lib.core.models.geojson.implementation.XyzProperties; import com.here.naksha.lib.core.models.naksha.Space; + import java.net.http.HttpResponse; import java.util.List; import java.util.UUID; + import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.skyscreamer.jsonassert.JSONAssert; @@ -36,271 +38,271 @@ public class UpdateFeatureTestHelper { - final @NotNull NakshaApp app; - final @NotNull NakshaTestWebClient nakshaClient; - - public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { - this.app = app; - this.nakshaClient = nakshaClient; - } - - void tc0500_testUpdateFeatures() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Preparation: create storage, event handler and space - final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); - nakshaClient.post("hub/storages", storage, streamId); - final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); - nakshaClient.post("hub/handlers", handler, streamId); - final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); - nakshaClient.post("hub/spaces", spaceJson, streamId); - // Read request body - final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0501_testUpdateFeatureById() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = bodyJson; - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0502_testUpdateFeatureByWrongUriId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(409, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(400, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0504_testUpdateFeaturesNoId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Read request body - final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - - final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(responseFeatureCollection); - final List inserted = responseFeatureCollection.getInserted(); - Assertions.assertEquals(2, inserted.size()); - } - - void tc0505_testUpdateFeaturesWithUuid() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - - final HttpResponse getResponse = - nakshaClient.get("hub/spaces/" + space.getId() + "/features/my-custom-id-301-2", streamId); - final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); - Assertions.assertNotNull(feature); - final XyzProperties newPropsOldUuid = feature.getProperties(); - final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); - final XyzProperties nullUuidProps = new XyzProperties(); - // Old UUID - newPropsOldUuid.put("speedLimit", "30"); - // New UUID - newPropsOutdatedUuid.put("speedLimit", "120"); - // Null UUID - nullUuidProps.put("uuid", null); - nullUuidProps.put("overriden", "yesyesyes"); - - // Execute request, correct UUID, should success - feature.setProperties(newPropsOldUuid); - final HttpResponse responseUpdateSuccess = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ -{ -"type": "FeatureCollection", -"features": [ -""" + feature + "]}", - streamId); - - // Perform first assertions - assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); - final XyzFeatureCollection responseFeatureCollection = - parseJson(responseUpdateSuccess.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(responseFeatureCollection); - final XyzFeature updatedFeature = - responseFeatureCollection.getFeatures().get(0); - Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); - - // Execute request, outdated UUID, should fail - feature.setProperties(newPropsOutdatedUuid); - final HttpResponse responseUpdateFail = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ -{ -"type": "FeatureCollection", -"features": [ -""" + feature + "]}", - streamId); - - // Perform second assertions - assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); - - // Execute request, null UUID, should success with overriding - feature.setProperties(nullUuidProps); - final HttpResponse responseOverriding = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ -{ -"type": "FeatureCollection", -"features": [ -""" + feature + "]}", - streamId); - - // Perform third assertions - assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); - final XyzFeatureCollection featureCollection = parseJson(responseOverriding.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(featureCollection); - final XyzFeature overridenFeature = featureCollection.getFeatures().get(0); - Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); - // Old properties like speedLimit should no longer be available - // The feature has been completely overwritten by the PUT request with null UUID - Assertions.assertFalse(overridenFeature.getProperties().containsKey("speedLimit")); - } - - void tc0506_testUpdateFeatureWithUuid() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - final String streamId = UUID.randomUUID().toString(); - - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - - final HttpResponse getResponse = - nakshaClient.get("hub/spaces/" + space.getId() + "/features/newly-inserted", streamId); - final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); - Assertions.assertNotNull(feature); - final XyzProperties newPropsOldUuid = feature.getProperties(); - final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); - final XyzProperties nullUuidProps = new XyzProperties(); - // Old UUID - newPropsOldUuid.put("speedLimit", "30"); - // New UUID - newPropsOutdatedUuid.put("speedLimit", "120"); - // Null UUID - nullUuidProps.put("uuid", null); - nullUuidProps.put("overriden", "yesyesyes"); - - // Execute request, correct UUID, should success - feature.setProperties(newPropsOldUuid); - final HttpResponse responseUpdateSuccess = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); - - // Perform first assertions - assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); - final XyzFeature updatedFeature = parseJson(responseUpdateSuccess.body(), XyzFeature.class); - Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); - Assertions.assertEquals("no", updatedFeature.getProperties().get("isImportant")); - - // Execute request, outdated UUID, should fail - feature.setProperties(newPropsOutdatedUuid); - final HttpResponse responseUpdateFail = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); - - // Perform second assertions - assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); - - // Execute request, null UUID, should success with overriding - feature.setProperties(nullUuidProps); - final HttpResponse responseOverriding = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); - - // Perform third assertions - assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); - final XyzFeature overridenFeature = parseJson(responseOverriding.body(), XyzFeature.class); - Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); - // Old properties like isImportant should no longer be available - // The feature has been completely overwritten by the PUT request with null UUID - Assertions.assertFalse(overridenFeature.getProperties().containsKey("isImportant")); - } + final @NotNull NakshaApp app; + final @NotNull NakshaTestWebClient nakshaClient; + + public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { + this.app = app; + this.nakshaClient = nakshaClient; + } + + void tc0500_testUpdateFeatures() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Preparation: create storage, event handler and space + final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); + nakshaClient.post("hub/storages", storage, streamId); + final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); + nakshaClient.post("hub/handlers", handler, streamId); + final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); + nakshaClient.post("hub/spaces", spaceJson, streamId); + // Read request body + final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0501_testUpdateFeatureById() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = bodyJson; + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0502_testUpdateFeatureByWrongUriId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(409, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(400, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0504_testUpdateFeaturesNoId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Read request body + final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + + final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final List inserted = responseFeatureCollection.getInserted(); + Assertions.assertEquals(2, inserted.size()); + } + + void tc0505_testUpdateFeaturesWithUuid() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + + final HttpResponse getResponse = + nakshaClient.get("hub/spaces/" + space.getId() + "/features/my-custom-id-301-2", streamId); + final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); + Assertions.assertNotNull(feature); + final XyzProperties newPropsOldUuid = feature.getProperties(); + final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); + final XyzProperties nullUuidProps = new XyzProperties(); + // Old UUID + newPropsOldUuid.put("speedLimit", "30"); + // New UUID + newPropsOutdatedUuid.put("speedLimit", "120"); + // Null UUID + nullUuidProps.put("uuid", null); + nullUuidProps.put("overriden", "yesyesyes"); + + // Execute request, correct UUID, should success + feature.setProperties(newPropsOldUuid); + final HttpResponse responseUpdateSuccess = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ + { + "type": "FeatureCollection", + "features": [ + """ + feature + "]}", + streamId); + + // Perform first assertions + assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); + final XyzFeatureCollection responseFeatureCollection = + parseJson(responseUpdateSuccess.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final XyzFeature updatedFeature = + responseFeatureCollection.getFeatures().get(0); + Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); + + // Execute request, outdated UUID, should fail + feature.setProperties(newPropsOutdatedUuid); + final HttpResponse responseUpdateFail = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ + { + "type": "FeatureCollection", + "features": [ + """ + feature + "]}", + streamId); + + // Perform second assertions + assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); + + // Execute request, null UUID, should success with overriding + feature.setProperties(nullUuidProps); + final HttpResponse responseOverriding = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ + { + "type": "FeatureCollection", + "features": [ + """ + feature + "]}", + streamId); + + // Perform third assertions + assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); + final XyzFeatureCollection featureCollection = parseJson(responseOverriding.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(featureCollection); + final XyzFeature overridenFeature = featureCollection.getFeatures().get(0); + Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); + // Old properties like speedLimit should no longer be available + // The feature has been completely overwritten by the PUT request with null UUID + Assertions.assertFalse(overridenFeature.getProperties().containsKey("speedLimit")); + } + + void tc0506_testUpdateFeatureWithUuid() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + final String streamId = UUID.randomUUID().toString(); + + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + + final HttpResponse getResponse = + nakshaClient.get("hub/spaces/" + space.getId() + "/features/newly-inserted", streamId); + final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); + Assertions.assertNotNull(feature); + final XyzProperties newPropsOldUuid = feature.getProperties(); + final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); + final XyzProperties nullUuidProps = new XyzProperties(); + // Old UUID + newPropsOldUuid.put("speedLimit", "30"); + // New UUID + newPropsOutdatedUuid.put("speedLimit", "120"); + // Null UUID + nullUuidProps.put("uuid", null); + nullUuidProps.put("overriden", "yesyesyes"); + + // Execute request, correct UUID, should success + feature.setProperties(newPropsOldUuid); + final HttpResponse responseUpdateSuccess = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform first assertions + assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); + final XyzFeature updatedFeature = parseJson(responseUpdateSuccess.body(), XyzFeature.class); + Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); + Assertions.assertEquals("no", updatedFeature.getProperties().get("isImportant")); + + // Execute request, outdated UUID, should fail + feature.setProperties(newPropsOutdatedUuid); + final HttpResponse responseUpdateFail = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform second assertions + assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); + + // Execute request, null UUID, should success with overriding + feature.setProperties(nullUuidProps); + final HttpResponse responseOverriding = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform third assertions + assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); + final XyzFeature overridenFeature = parseJson(responseOverriding.body(), XyzFeature.class); + Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); + // Old properties like isImportant should no longer be available + // The feature has been completely overwritten by the PUT request with null UUID + Assertions.assertFalse(overridenFeature.getProperties().containsKey("isImportant")); + } } From 00f13e2492e34bcf0cbeeebc918dbca963f5e43b Mon Sep 17 00:00:00 2001 From: phmai Date: Mon, 27 Nov 2023 12:00:20 +0100 Subject: [PATCH 20/31] renaming correctly to upsert --- .../naksha/app/service/http/apis/WriteFeatureApi.java | 5 +++-- .../app/service/http/tasks/WriteFeatureApiTask.java | 9 +++++---- .../src/main/resources/swagger/openapi.yaml | 7 +++++-- .../here/naksha/lib/core/util/storage/RequestHelper.java | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java index dc3888f98..14737b647 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java @@ -42,6 +42,7 @@ public WriteFeatureApi(final @NotNull NakshaHttpVerticle verticle) { public void addOperations(final @NotNull RouterBuilder rb) { rb.operation("postFeatures").handler(this::createFeatures); rb.operation("putFeatures").handler(this::updateFeatures); + rb.operation("putFeatures").handler(this::upsertFeatures); rb.operation("putFeature").handler(this::updateFeature); } @@ -52,8 +53,8 @@ private void createFeatures(final @NotNull RoutingContext routingContext) { startWriteFeatureApiTask(CREATE_FEATURES, routingContext); } - private void updateFeatures(final @NotNull RoutingContext routingContext) { - startWriteFeatureApiTask(MODIFY_FEATURES, routingContext); + private void upsertFeatures(final @NotNull RoutingContext routingContext) { + startWriteFeatureApiTask(UPSERT_FEATURES, routingContext); } private void updateFeature(final @NotNull RoutingContext routingContext) { diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index 565fb5672..8d989dd29 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -54,8 +54,9 @@ public class WriteFeatureApiTask extends AbstractApiTask< public enum WriteFeatureApiReqType { CREATE_FEATURES, - MODIFY_FEATURES, - UPDATE_BY_ID + UPSERT_FEATURES, + UPDATE_BY_ID, + DELETE_FEATURES } public WriteFeatureApiTask( @@ -86,7 +87,7 @@ protected void init() {} try { return switch (this.reqType) { case CREATE_FEATURES -> executeCreateFeatures(); - case MODIFY_FEATURES -> executeUpdateFeatures(); + case UPSERT_FEATURES -> executeUpsertFeatures(); case UPDATE_BY_ID -> executeUpdateFeature(); default -> executeUnsupported(); }; @@ -136,7 +137,7 @@ protected void init() {} return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class); } - private @NotNull XyzResponse executeUpdateFeatures() throws Exception { + private @NotNull XyzResponse executeUpsertFeatures() throws Exception { // Deserialize input request final FeatureCollectionRequest collectionRequest = featuresFromRequestBody(); final List features = (List) collectionRequest.getFeatures(); diff --git a/here-naksha-app-service/src/main/resources/swagger/openapi.yaml b/here-naksha-app-service/src/main/resources/swagger/openapi.yaml index 58b81dd16..89c9da2aa 100644 --- a/here-naksha-app-service/src/main/resources/swagger/openapi.yaml +++ b/here-naksha-app-service/src/main/resources/swagger/openapi.yaml @@ -479,9 +479,12 @@ paths: put: tags: - Write Features - summary: Update features in the space + summary: Upsert features in the space description: > - This method allows to update features in the storage associated (directly or via event handler) with the space. + This method allows to upsert features in the storage associated (directly or via event handler) with the space. + If a feature does not exist yet, it is created, if it is already available, it will be updated. + For each existing feature, if UUID is specified and matches the current UUID stored in Naksha, the feature will be updated. + If the UUID does not match, HTTP code 409 will be returned. It ensures an atomic operation, so either all features will be updated or none (in case of failure). operationId: putFeatures parameters: diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java index dfb48bffb..d325288a0 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java @@ -112,7 +112,7 @@ public class RequestHelper { } /** - * Helper method to create WriteFeatures request for updating multiple features. + * Helper method to create WriteFeatures request for upserting multiple features. * * @param collectionName name of the storage collection * @param features feature object array to be updated From 31811abe9adc59c351fefcbfada024ec01bee8c5 Mon Sep 17 00:00:00 2001 From: phmai Date: Mon, 27 Nov 2023 12:08:22 +0100 Subject: [PATCH 21/31] fix cherry pick error --- .../com/here/naksha/app/service/http/apis/WriteFeatureApi.java | 1 - 1 file changed, 1 deletion(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java index 14737b647..06543c6fb 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/apis/WriteFeatureApi.java @@ -41,7 +41,6 @@ public WriteFeatureApi(final @NotNull NakshaHttpVerticle verticle) { @Override public void addOperations(final @NotNull RouterBuilder rb) { rb.operation("postFeatures").handler(this::createFeatures); - rb.operation("putFeatures").handler(this::updateFeatures); rb.operation("putFeatures").handler(this::upsertFeatures); rb.operation("putFeature").handler(this::updateFeature); } From 7c91dd39ef600d9f4d291c1528f6eb517887e9cd Mon Sep 17 00:00:00 2001 From: phmai Date: Mon, 27 Nov 2023 12:09:19 +0100 Subject: [PATCH 22/31] autoformat --- .../http/tasks/WriteFeatureApiTask.java | 6 +- .../app/service/UpdateFeatureTestHelper.java | 536 +++++++++--------- 2 files changed, 270 insertions(+), 272 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index 8d989dd29..0bf457a61 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -18,11 +18,11 @@ */ package com.here.naksha.app.service.http.tasks; -import static com.here.naksha.app.service.http.apis.ApiParams.FEATURE_ID; -import static com.here.naksha.app.service.http.apis.ApiParams.SPACE_ID; import static com.here.naksha.app.service.http.apis.ApiParams.ADD_TAGS; -import static com.here.naksha.app.service.http.apis.ApiParams.REMOVE_TAGS; +import static com.here.naksha.app.service.http.apis.ApiParams.FEATURE_ID; import static com.here.naksha.app.service.http.apis.ApiParams.PREFIX_ID; +import static com.here.naksha.app.service.http.apis.ApiParams.REMOVE_TAGS; +import static com.here.naksha.app.service.http.apis.ApiParams.SPACE_ID; import static com.here.naksha.app.service.http.apis.ApiParams.pathParam; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index bef469ec7..a04c8bfb6 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -26,11 +26,9 @@ import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; import com.here.naksha.lib.core.models.geojson.implementation.XyzProperties; import com.here.naksha.lib.core.models.naksha.Space; - import java.net.http.HttpResponse; import java.util.List; import java.util.UUID; - import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.skyscreamer.jsonassert.JSONAssert; @@ -38,271 +36,271 @@ public class UpdateFeatureTestHelper { - final @NotNull NakshaApp app; - final @NotNull NakshaTestWebClient nakshaClient; - - public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { - this.app = app; - this.nakshaClient = nakshaClient; - } - - void tc0500_testUpdateFeatures() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Preparation: create storage, event handler and space - final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); - nakshaClient.post("hub/storages", storage, streamId); - final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); - nakshaClient.post("hub/handlers", handler, streamId); - final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); - nakshaClient.post("hub/spaces", spaceJson, streamId); - // Read request body - final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0501_testUpdateFeatureById() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = bodyJson; - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0502_testUpdateFeatureByWrongUriId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(409, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(400, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0504_testUpdateFeaturesNoId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Read request body - final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - - final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(responseFeatureCollection); - final List inserted = responseFeatureCollection.getInserted(); - Assertions.assertEquals(2, inserted.size()); - } - - void tc0505_testUpdateFeaturesWithUuid() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - - final HttpResponse getResponse = - nakshaClient.get("hub/spaces/" + space.getId() + "/features/my-custom-id-301-2", streamId); - final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); - Assertions.assertNotNull(feature); - final XyzProperties newPropsOldUuid = feature.getProperties(); - final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); - final XyzProperties nullUuidProps = new XyzProperties(); - // Old UUID - newPropsOldUuid.put("speedLimit", "30"); - // New UUID - newPropsOutdatedUuid.put("speedLimit", "120"); - // Null UUID - nullUuidProps.put("uuid", null); - nullUuidProps.put("overriden", "yesyesyes"); - - // Execute request, correct UUID, should success - feature.setProperties(newPropsOldUuid); - final HttpResponse responseUpdateSuccess = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ - { - "type": "FeatureCollection", - "features": [ - """ + feature + "]}", - streamId); - - // Perform first assertions - assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); - final XyzFeatureCollection responseFeatureCollection = - parseJson(responseUpdateSuccess.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(responseFeatureCollection); - final XyzFeature updatedFeature = - responseFeatureCollection.getFeatures().get(0); - Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); - - // Execute request, outdated UUID, should fail - feature.setProperties(newPropsOutdatedUuid); - final HttpResponse responseUpdateFail = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ - { - "type": "FeatureCollection", - "features": [ - """ + feature + "]}", - streamId); - - // Perform second assertions - assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); - - // Execute request, null UUID, should success with overriding - feature.setProperties(nullUuidProps); - final HttpResponse responseOverriding = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ - { - "type": "FeatureCollection", - "features": [ - """ + feature + "]}", - streamId); - - // Perform third assertions - assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); - final XyzFeatureCollection featureCollection = parseJson(responseOverriding.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(featureCollection); - final XyzFeature overridenFeature = featureCollection.getFeatures().get(0); - Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); - // Old properties like speedLimit should no longer be available - // The feature has been completely overwritten by the PUT request with null UUID - Assertions.assertFalse(overridenFeature.getProperties().containsKey("speedLimit")); - } - - void tc0506_testUpdateFeatureWithUuid() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - final String streamId = UUID.randomUUID().toString(); - - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - - final HttpResponse getResponse = - nakshaClient.get("hub/spaces/" + space.getId() + "/features/newly-inserted", streamId); - final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); - Assertions.assertNotNull(feature); - final XyzProperties newPropsOldUuid = feature.getProperties(); - final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); - final XyzProperties nullUuidProps = new XyzProperties(); - // Old UUID - newPropsOldUuid.put("speedLimit", "30"); - // New UUID - newPropsOutdatedUuid.put("speedLimit", "120"); - // Null UUID - nullUuidProps.put("uuid", null); - nullUuidProps.put("overriden", "yesyesyes"); - - // Execute request, correct UUID, should success - feature.setProperties(newPropsOldUuid); - final HttpResponse responseUpdateSuccess = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); - - // Perform first assertions - assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); - final XyzFeature updatedFeature = parseJson(responseUpdateSuccess.body(), XyzFeature.class); - Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); - Assertions.assertEquals("no", updatedFeature.getProperties().get("isImportant")); - - // Execute request, outdated UUID, should fail - feature.setProperties(newPropsOutdatedUuid); - final HttpResponse responseUpdateFail = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); - - // Perform second assertions - assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); - - // Execute request, null UUID, should success with overriding - feature.setProperties(nullUuidProps); - final HttpResponse responseOverriding = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); - - // Perform third assertions - assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); - final XyzFeature overridenFeature = parseJson(responseOverriding.body(), XyzFeature.class); - Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); - // Old properties like isImportant should no longer be available - // The feature has been completely overwritten by the PUT request with null UUID - Assertions.assertFalse(overridenFeature.getProperties().containsKey("isImportant")); - } + final @NotNull NakshaApp app; + final @NotNull NakshaTestWebClient nakshaClient; + + public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { + this.app = app; + this.nakshaClient = nakshaClient; + } + + void tc0500_testUpdateFeatures() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Preparation: create storage, event handler and space + final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); + nakshaClient.post("hub/storages", storage, streamId); + final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); + nakshaClient.post("hub/handlers", handler, streamId); + final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); + nakshaClient.post("hub/spaces", spaceJson, streamId); + // Read request body + final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0501_testUpdateFeatureById() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = bodyJson; + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0502_testUpdateFeatureByWrongUriId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(409, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(400, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0504_testUpdateFeaturesNoId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Read request body + final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + + final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final List inserted = responseFeatureCollection.getInserted(); + Assertions.assertEquals(2, inserted.size()); + } + + void tc0505_testUpdateFeaturesWithUuid() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + + final HttpResponse getResponse = + nakshaClient.get("hub/spaces/" + space.getId() + "/features/my-custom-id-301-2", streamId); + final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); + Assertions.assertNotNull(feature); + final XyzProperties newPropsOldUuid = feature.getProperties(); + final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); + final XyzProperties nullUuidProps = new XyzProperties(); + // Old UUID + newPropsOldUuid.put("speedLimit", "30"); + // New UUID + newPropsOutdatedUuid.put("speedLimit", "120"); + // Null UUID + nullUuidProps.put("uuid", null); + nullUuidProps.put("overriden", "yesyesyes"); + + // Execute request, correct UUID, should success + feature.setProperties(newPropsOldUuid); + final HttpResponse responseUpdateSuccess = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ +{ +"type": "FeatureCollection", +"features": [ +""" + feature + "]}", + streamId); + + // Perform first assertions + assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); + final XyzFeatureCollection responseFeatureCollection = + parseJson(responseUpdateSuccess.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final XyzFeature updatedFeature = + responseFeatureCollection.getFeatures().get(0); + Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); + + // Execute request, outdated UUID, should fail + feature.setProperties(newPropsOutdatedUuid); + final HttpResponse responseUpdateFail = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ +{ +"type": "FeatureCollection", +"features": [ +""" + feature + "]}", + streamId); + + // Perform second assertions + assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); + + // Execute request, null UUID, should success with overriding + feature.setProperties(nullUuidProps); + final HttpResponse responseOverriding = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ +{ +"type": "FeatureCollection", +"features": [ +""" + feature + "]}", + streamId); + + // Perform third assertions + assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); + final XyzFeatureCollection featureCollection = parseJson(responseOverriding.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(featureCollection); + final XyzFeature overridenFeature = featureCollection.getFeatures().get(0); + Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); + // Old properties like speedLimit should no longer be available + // The feature has been completely overwritten by the PUT request with null UUID + Assertions.assertFalse(overridenFeature.getProperties().containsKey("speedLimit")); + } + + void tc0506_testUpdateFeatureWithUuid() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + final String streamId = UUID.randomUUID().toString(); + + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + + final HttpResponse getResponse = + nakshaClient.get("hub/spaces/" + space.getId() + "/features/newly-inserted", streamId); + final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); + Assertions.assertNotNull(feature); + final XyzProperties newPropsOldUuid = feature.getProperties(); + final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); + final XyzProperties nullUuidProps = new XyzProperties(); + // Old UUID + newPropsOldUuid.put("speedLimit", "30"); + // New UUID + newPropsOutdatedUuid.put("speedLimit", "120"); + // Null UUID + nullUuidProps.put("uuid", null); + nullUuidProps.put("overriden", "yesyesyes"); + + // Execute request, correct UUID, should success + feature.setProperties(newPropsOldUuid); + final HttpResponse responseUpdateSuccess = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform first assertions + assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); + final XyzFeature updatedFeature = parseJson(responseUpdateSuccess.body(), XyzFeature.class); + Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); + Assertions.assertEquals("no", updatedFeature.getProperties().get("isImportant")); + + // Execute request, outdated UUID, should fail + feature.setProperties(newPropsOutdatedUuid); + final HttpResponse responseUpdateFail = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform second assertions + assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); + + // Execute request, null UUID, should success with overriding + feature.setProperties(nullUuidProps); + final HttpResponse responseOverriding = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform third assertions + assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); + final XyzFeature overridenFeature = parseJson(responseOverriding.body(), XyzFeature.class); + Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); + // Old properties like isImportant should no longer be available + // The feature has been completely overwritten by the PUT request with null UUID + Assertions.assertFalse(overridenFeature.getProperties().containsKey("isImportant")); + } } From 6736ad320880b3789fc91f0b3e4f4720bb4b036c Mon Sep 17 00:00:00 2001 From: phmai Date: Mon, 27 Nov 2023 17:09:26 +0100 Subject: [PATCH 23/31] decouple upsert and update tests completely from create --- .../src/main/resources/swagger/openapi.yaml | 3 +++ .../naksha/app/service/UpdateFeatureTestHelper.java | 10 ++++++---- .../TC0500_updateFeatures/create_features.json | 4 ++-- .../TC0500_updateFeatures/response_no_geometry.json | 6 +++--- .../TC0500_updateFeatures/update_request.json | 4 ++-- .../update_request_and_response.json | 2 +- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/here-naksha-app-service/src/main/resources/swagger/openapi.yaml b/here-naksha-app-service/src/main/resources/swagger/openapi.yaml index 89c9da2aa..c56c680b3 100644 --- a/here-naksha-app-service/src/main/resources/swagger/openapi.yaml +++ b/here-naksha-app-service/src/main/resources/swagger/openapi.yaml @@ -543,6 +543,9 @@ paths: summary: Update feature with specified ID in the space description: > This method allows to update one feature in the storage associated (directly or via event handler) with the space. + If the feature with that ID does not exist, HTTP code 404 is returned. + If UUID is specified and matches the current UUID stored in Naksha, the feature will be updated. + If the UUID does not match, HTTP code 409 will be returned. operationId: putFeature parameters: - $ref: '#/components/parameters/AddTags' diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index a04c8bfb6..cd281e039 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -48,17 +48,19 @@ void tc0500_testUpdateFeatures() throws Exception { // Test API : PUT /hub/spaces/{spaceId}/features final String streamId = UUID.randomUUID().toString(); - // Preparation: create storage, event handler and space + // Preparation: create storage, event handler, space, and initial features final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); nakshaClient.post("hub/storages", storage, streamId); final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); nakshaClient.post("hub/handlers", handler, streamId); final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); nakshaClient.post("hub/spaces", spaceJson, streamId); + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String createFeaturesJson = loadFileOrFail("TC0500_updateFeatures/create_features.json"); + nakshaClient.post("hub/spaces/" + space.getId() + "/features", createFeaturesJson, streamId); // Read request body final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); // When: Create Features request is submitted to NakshaHub Space Storage instance @@ -87,7 +89,7 @@ void tc0501_testUpdateFeatureById() throws Exception { // When: Create Features request is submitted to NakshaHub Space Storage instance final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-feature-1", bodyJson, streamId); // Then: Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -135,7 +137,7 @@ void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { // When: Create Features request is submitted to NakshaHub Space Storage instance final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-id-301-1", bodyJson, streamId); + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-feature-1", bodyJson, streamId); // Then: Perform assertions assertEquals(400, response.statusCode(), "ResCode mismatch"); diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_features.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_features.json index 865315c72..a80fb4284 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_features.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/create_features.json @@ -2,7 +2,7 @@ "type": "FeatureCollection", "features": [ { - "id": "my-custom-id-301-1", + "id": "my-custom-feature-1", "type": "Feature", "properties": { "speedLimit": "60" @@ -13,7 +13,7 @@ } }, { - "id": "my-custom-id-301-2", + "id": "my-custom-feature-2", "type": "Feature", "properties": { "speedLimit": "80" diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json index 3b4d3dfae..7821e14bb 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/response_no_geometry.json @@ -2,14 +2,14 @@ "type": "FeatureCollection", "features": [ { - "id": "my-custom-id-301-1", + "id": "my-custom-feature-1", "type": "Feature", "properties": { "speedLimit": "50" } }, { - "id": "my-custom-id-301-2", + "id": "my-custom-feature-2", "type": "Feature", "properties": { "speedLimit": "70" @@ -23,6 +23,6 @@ } } ], - "updated": ["my-custom-id-301-1","my-custom-id-301-2"], + "updated": ["my-custom-feature-1","my-custom-feature-2"], "inserted": ["newly-inserted"] } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json index fcea91e73..431f8b533 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0500_updateFeatures/update_request.json @@ -2,14 +2,14 @@ "type": "FeatureCollection", "features": [ { - "id": "my-custom-id-301-1", + "id": "my-custom-feature-1", "type": "Feature", "properties": { "speedLimit": "50" } }, { - "id": "my-custom-id-301-2", + "id": "my-custom-feature-2", "type": "Feature", "properties": { "speedLimit": "70" diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request_and_response.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request_and_response.json index d63312d9b..33cef75b6 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request_and_response.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0501_updateOneFeatureById/update_request_and_response.json @@ -1,5 +1,5 @@ { - "id": "my-custom-id-301-1", + "id": "my-custom-feature-1", "type": "Feature", "properties": { "speedLimit": "30" From 77234e01b0eff03fdc258f02a836946537fe90c2 Mon Sep 17 00:00:00 2001 From: phmai Date: Mon, 27 Nov 2023 17:53:19 +0100 Subject: [PATCH 24/31] improve test assertion --- .../app/service/UpdateFeatureTestHelper.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index cd281e039..9b5dc8fcf 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -19,7 +19,7 @@ package com.here.naksha.app.service; import static com.here.naksha.app.common.TestUtil.*; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import com.here.naksha.app.common.NakshaTestWebClient; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; @@ -33,6 +33,7 @@ import org.junit.jupiter.api.Assertions; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; +import org.skyscreamer.jsonassert.comparator.ArraySizeComparator; public class UpdateFeatureTestHelper { @@ -168,7 +169,21 @@ void tc0504_testUpdateFeaturesNoId() throws Exception { final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); Assertions.assertNotNull(responseFeatureCollection); final List inserted = responseFeatureCollection.getInserted(); - Assertions.assertEquals(2, inserted.size()); + final List insertedFeatures = responseFeatureCollection.getFeatures(); + JSONAssert.assertEquals( + "{inserted:[" + inserted.size() + "]}", + response.body(), + new ArraySizeComparator(JSONCompareMode.LENIENT)); + + for (int i = 0; i < inserted.size(); i++) { + assertEquals( + inserted.get(i), + insertedFeatures.get(i).getId(), + "Mismatch between inserted v/s feature ID in the response at idx : " + i); + assertNotNull( + insertedFeatures.get(i).getProperties().getXyzNamespace().getUuid(), + "UUID found missing in response for feature at idx : " + i); + } } void tc0505_testUpdateFeaturesWithUuid() throws Exception { From 648f0ed614aa9dd85fc66a694c18c3966569a041 Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 28 Nov 2023 10:30:22 +0100 Subject: [PATCH 25/31] change mock and test to return 404 for updating non existent features --- .../java/com/here/naksha/app/service/NakshaAppTest.java | 6 +++--- .../here/naksha/app/service/UpdateFeatureTestHelper.java | 2 +- .../TC0061_updateNonexistentStorage/response.json | 2 +- .../TC0161_updateNonexistentEventHandler/response.json | 2 +- .../TC0261_updateNonexistentSpace/response.json | 2 +- .../TC0502_updateFeatureWithWrongUriId/response.json | 2 +- .../com/here/naksha/lib/hub/mock/NHAdminWriterMock.java | 5 +++-- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index 2b4988158..47daf0dd4 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -210,7 +210,7 @@ void tc0061_testUpdateNonexistentStorage() throws Exception { nakshaClient.put("hub/storages/this-id-does-not-exist", updateStorageJson, streamId); // Then: - assertEquals(409, response.statusCode()); + assertEquals(404, response.statusCode()); JSONAssert.assertEquals(expectedErrorResponse, response.body(), JSONCompareMode.LENIENT); assertEquals(streamId, getHeader(response, HDR_STREAM_ID)); } @@ -400,7 +400,7 @@ void tc0161_testUpdateNonexistentEventHandler() throws Exception { nakshaClient.put("hub/handlers/non-existent-test-handler", updateEventHandlerJson, streamId); // Then: - assertEquals(409, response.statusCode()); + assertEquals(404, response.statusCode()); JSONAssert.assertEquals(expectedRespBody, response.body(), JSONCompareMode.LENIENT); assertEquals(streamId, getHeader(response, HDR_STREAM_ID)); } @@ -545,7 +545,7 @@ void tc0261_testUpdateNonexistentSpace() throws Exception { nakshaClient.put("hub/spaces/non-existent-space", updateSpaceJson, streamId); // Then: - assertEquals(409, response.statusCode()); + assertEquals(404, response.statusCode()); JSONAssert.assertEquals(expectedErrorResponse, response.body(), JSONCompareMode.LENIENT); assertEquals(streamId, getHeader(response, HDR_STREAM_ID)); } diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index 9b5dc8fcf..cb991085b 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -117,7 +117,7 @@ void tc0502_testUpdateFeatureByWrongUriId() throws Exception { nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); // Then: Perform assertions - assertEquals(409, response.statusCode(), "ResCode mismatch"); + assertEquals(404, response.statusCode(), "ResCode mismatch"); JSONAssert.assertEquals( "Update Feature error response doesn't match", expectedBodyPart, diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0061_updateNonexistentStorage/response.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0061_updateNonexistentStorage/response.json index 5a38beab3..4c48fbc0b 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0061_updateNonexistentStorage/response.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0061_updateNonexistentStorage/response.json @@ -1,5 +1,5 @@ { "type": "ErrorResponse", - "error": "Conflict", + "error": "NotFound", "errorMessage": "No feature found for id this-id-does-not-exist" } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0161_updateNonexistentEventHandler/response.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0161_updateNonexistentEventHandler/response.json index 525ad6dd6..2a7085870 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0161_updateNonexistentEventHandler/response.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0161_updateNonexistentEventHandler/response.json @@ -1,5 +1,5 @@ { "type": "ErrorResponse", - "error": "Conflict", + "error": "NotFound", "errorMessage": "No feature found for id non-existent-test-handler" } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0261_updateNonexistentSpace/response.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0261_updateNonexistentSpace/response.json index df5d68505..b1d41a386 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0261_updateNonexistentSpace/response.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0261_updateNonexistentSpace/response.json @@ -1,5 +1,5 @@ { "type": "ErrorResponse", - "error": "Conflict", + "error": "NotFound", "errorMessage": "No feature found for id non-existent-space" } diff --git a/here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/response.json b/here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/response.json index a4eb75c5c..c79c9e58a 100644 --- a/here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/response.json +++ b/here-naksha-app-service/src/test/resources/unit_test_data/TC0502_updateFeatureWithWrongUriId/response.json @@ -1,5 +1,5 @@ { "type": "ErrorResponse", - "error": "Conflict", + "error": "NotFound", "errorMessage": "No feature found for id wrong-id" } diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/NHAdminWriterMock.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/NHAdminWriterMock.java index 5036e9e2d..ab91f9c16 100644 --- a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/NHAdminWriterMock.java +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/NHAdminWriterMock.java @@ -135,6 +135,8 @@ public NHAdminWriterMock(final @NotNull Map> mockCol final String state = sqe.getSQLState(); if (PSQLState.UNIQUE_VIOLATION.getState().equals(state)) { error = XyzError.CONFLICT; + } else if (PSQLState.NO_DATA.getState().equals(state)) { + error = XyzError.NOT_FOUND; } return new ErrorResult(error, sqe.getMessage(), sqe); } @@ -157,8 +159,7 @@ public NHAdminWriterMock(final @NotNull Map> mockCol mockCollection.get(collectionId).compute(newF.getId(), (fId, oldF) -> { // no existing feature to update if (oldF == null) { - exception.set( - new SQLException("No feature found for id " + fId, PSQLState.UNIQUE_VIOLATION.getState())); + exception.set(new SQLException("No feature found for id " + fId, PSQLState.NO_DATA.getState())); return oldF; } // update if UUID matches (or overwrite if new uuid is missing) From 602e69982b3d2eab72890551fbed328931ccede4 Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 28 Nov 2023 11:10:36 +0100 Subject: [PATCH 26/31] fix conflict with maintenance branch --- .../service/http/tasks/WriteFeatureApiTask.java | 8 ++++---- .../lib/core/util/storage/RequestHelper.java | 14 +++++++------- .../naksha/lib/core/util/storage/ResultHelper.java | 13 ++++++------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index 1519da18c..9c63d60b3 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -164,12 +164,12 @@ protected void init() {} feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true); } } - final WriteFeatures wrRequest = RequestHelper.upsertFeaturesRequest(spaceId, features); + final WriteXyzFeatures wrRequest = RequestHelper.upsertFeaturesRequest(spaceId, features); // Forward request to NH Space Storage writer instance final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest); // transform WriteResult to Http FeatureCollection response - return transformWriteResultToXyzCollectionResponse(wrResult, Storage.class); + return transformWriteResultToXyzCollectionResponse(wrResult, XyzFeature.class); } private @NotNull XyzResponse executeUpdateFeature() throws Exception { @@ -212,12 +212,12 @@ protected void init() {} feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true); } - final WriteFeatures wrRequest = RequestHelper.updateFeatureRequest(spaceId, feature); + final WriteXyzFeatures wrRequest = RequestHelper.updateFeatureRequest(spaceId, feature); // Forward request to NH Space Storage writer instance final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest); // transform WriteResult to Http FeatureCollection response - return transformWriteResultToXyzFeatureResponse(wrResult, Storage.class); + return transformWriteResultToXyzFeatureResponse(wrResult, XyzFeature.class); } private @NotNull FeatureCollectionRequest featuresFromRequestBody() throws JsonProcessingException { diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java index 5d6f7e969..6531166c9 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/RequestHelper.java @@ -122,16 +122,16 @@ public class RequestHelper { * * @param collectionName name of the storage collection * @param features feature object array to be updated - * @param any object extending XyzFeature + * @param any object extending XyzFeature * @return WriteFeatures request that can be used against IStorage methods */ - public static @NotNull WriteFeatures upsertFeaturesRequest( - final @NotNull String collectionName, final @NotNull List features) { - final List<@NotNull WriteOp> opList = new ArrayList<>(); - for (final T feature : features) { - opList.add(new WriteXyzOp<>(EWriteOp.PUT, feature)); + public static @NotNull WriteXyzFeatures upsertFeaturesRequest( + final @NotNull String collectionName, final @NotNull List features) { + final WriteXyzFeatures request = new WriteXyzFeatures(collectionName); + for (FEATURE feature : features) { + request.put(feature); } - return new WriteFeatures<>(collectionName, opList); + return request; } /** diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java index ec2799692..d197a0b67 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/util/storage/ResultHelper.java @@ -20,15 +20,14 @@ import com.here.naksha.lib.core.exceptions.NoCursor; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; -import com.here.naksha.lib.core.models.storage.ForwardCursor; import com.here.naksha.lib.core.models.storage.EExecutedOp; +import com.here.naksha.lib.core.models.storage.ForwardCursor; import com.here.naksha.lib.core.models.storage.Result; import com.here.naksha.lib.core.models.storage.XyzFeatureCodec; +import java.util.*; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; -import com.here.naksha.lib.core.models.storage.ResultCursor; -import java.util.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -113,7 +112,7 @@ public static List readFeaturesFromResult(Result resul */ public static Map> readFeaturesGroupedByOp( Result result, Class featureType, long limit) throws NoCursor, NoSuchElementException { - try (ResultCursor resultCursor = result.cursor(featureType)) { + try (ForwardCursor resultCursor = result.getXyzFeatureCursor()) { if (!resultCursor.hasNext()) { throw new NoSuchElementException("Result Cursor is empty"); } @@ -126,11 +125,11 @@ public static Map> readFeaturesGroup throw new RuntimeException("Unexpected invalid result"); } if (resultCursor.getOp().equals(EExecutedOp.CREATED)) { - insertedFeatures.add(resultCursor.getFeature()); + insertedFeatures.add(featureType.cast(resultCursor.getFeature())); } else if (resultCursor.getOp().equals(EExecutedOp.UPDATED)) { - updatedFeatures.add(resultCursor.getFeature()); + updatedFeatures.add(featureType.cast(resultCursor.getFeature())); } else if (resultCursor.getOp().equals(EExecutedOp.DELETED)) { - deletedFeatures.add(resultCursor.getFeature()); + deletedFeatures.add(featureType.cast(resultCursor.getFeature())); } } final Map> features = new HashMap<>(); From 8d605e32547874a429008dbd9d4a47ba9fc98b16 Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 28 Nov 2023 11:22:44 +0100 Subject: [PATCH 27/31] update openapi yaml --- .../src/main/resources/swagger/openapi.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/here-naksha-app-service/src/main/resources/swagger/openapi.yaml b/here-naksha-app-service/src/main/resources/swagger/openapi.yaml index c56c680b3..8697a0c98 100644 --- a/here-naksha-app-service/src/main/resources/swagger/openapi.yaml +++ b/here-naksha-app-service/src/main/resources/swagger/openapi.yaml @@ -155,6 +155,8 @@ paths: $ref: '#/components/responses/ErrorResponse401' '403': $ref: '#/components/responses/ErrorResponse403' + '404': + $ref: '#/components/responses/ErrorResponse404' '409': $ref: '#/components/responses/ErrorResponse409' '429': @@ -273,6 +275,8 @@ paths: $ref: '#/components/responses/ErrorResponse401' '403': $ref: '#/components/responses/ErrorResponse403' + '404': + $ref: '#/components/responses/ErrorResponse404' '409': $ref: '#/components/responses/ErrorResponse409' '429': @@ -391,6 +395,8 @@ paths: $ref: '#/components/responses/ErrorResponse401' '403': $ref: '#/components/responses/ErrorResponse403' + '404': + $ref: '#/components/responses/ErrorResponse404' '409': $ref: '#/components/responses/ErrorResponse409' '429': From eff473271f70aea88df3009fd87b710f789e6771 Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 28 Nov 2023 15:43:37 +0100 Subject: [PATCH 28/31] fix mock --- .../here/naksha/lib/hub/mock/NHAdminWriterMock.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/NHAdminWriterMock.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/NHAdminWriterMock.java index 879f7b169..58813105b 100644 --- a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/NHAdminWriterMock.java +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/mock/NHAdminWriterMock.java @@ -166,9 +166,9 @@ public NHAdminWriterMock(final @NotNull Map> mockCol } // update if UUID matches (or overwrite if new uuid is missing) final XyzFeature ef = (XyzFeature) oldF; - if (uuidOf(ef).equals(uuidOf(feature)) || uuidOf(feature) == null) { + if ((Objects.equals(uuidOf(ef), uuidOf(feature)) && uuidOf(feature) != null) || uuidOf(feature) == null) { result.set(featureCodec(feature, EExecutedOp.UPDATED)); - return feature; + return setUuidFor(feature); } else { // throw error if UUID mismatches exception.set(new SQLException( @@ -190,14 +190,17 @@ public NHAdminWriterMock(final @NotNull Map> mockCol mockCollection.get(collectionId).compute(feature.getId(), (fId, oldF) -> { // insert if missing if (oldF == null) { + if (uuidOf(feature) == null) { + setUuidFor(feature); + } result.set(featureCodec(feature, EExecutedOp.CREATED)); return feature; } // update if UUID matches (or overwrite if new uuid is missing) final XyzFeature ef = (XyzFeature) oldF; - if (uuidOf(ef).equals(uuidOf(feature)) || uuidOf(feature) == null) { + if ((Objects.equals(uuidOf(ef), uuidOf(feature)) && uuidOf(feature) != null) || uuidOf(feature) == null) { result.set(featureCodec(feature, EExecutedOp.UPDATED)); - return feature; + return setUuidFor(feature); } else { // throw error if UUID mismatches exception.set(new SQLException( @@ -227,7 +230,7 @@ public NHAdminWriterMock(final @NotNull Map> mockCol } // delete if UUID matches final XyzFeature ef = (XyzFeature) oldF; - if (uuidOf(ef).equals(uuid)) { + if (Objects.equals(uuidOf(ef), uuid)) { result.set(featureCodec(ef, EExecutedOp.DELETED)); return null; } else { From 719a5bb53ef3c816d8eca420324be09313160a1f Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 28 Nov 2023 16:19:33 +0100 Subject: [PATCH 29/31] clean up --- .../http/tasks/WriteFeatureApiTask.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java index 9c63d60b3..013ad4493 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/WriteFeatureApiTask.java @@ -122,12 +122,13 @@ protected void init() {} } // as applicable, modify features based on parameters supplied - if (prefixId != null || addTags != null || removeTags != null) { - for (final XyzFeature feature : features) { - feature.setIdPrefix(prefixId); - feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true); - } + + for (final XyzFeature feature : features) { + feature.setIdPrefix(prefixId); + addTagsToFeature(feature, addTags); + removeTagsFromFeature(feature, removeTags); } + final WriteXyzFeatures wrRequest = RequestHelper.createFeaturesRequest(spaceId, features); // Forward request to NH Space Storage writer instance @@ -159,10 +160,9 @@ protected void init() {} } // as applicable, modify features based on parameters supplied - if (addTags != null || removeTags != null) { - for (final XyzFeature feature : features) { - feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true); - } + for (final XyzFeature feature : features) { + addTagsToFeature(feature, addTags); + removeTagsFromFeature(feature, removeTags); } final WriteXyzFeatures wrRequest = RequestHelper.upsertFeaturesRequest(spaceId, features); @@ -208,9 +208,8 @@ protected void init() {} } // as applicable, modify features based on parameters supplied - if (addTags != null || removeTags != null) { - feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true); - } + addTagsToFeature(feature, addTags); + removeTagsFromFeature(feature, removeTags); final WriteXyzFeatures wrRequest = RequestHelper.updateFeatureRequest(spaceId, feature); @@ -228,4 +227,16 @@ protected void init() {} .readValue(bodyJson); } } + + private void addTagsToFeature(XyzFeature feature, List addTags) { + if (addTags != null) { + feature.getProperties().getXyzNamespace().addTags(addTags, true); + } + } + + private void removeTagsFromFeature(XyzFeature feature, List removeTags) { + if (removeTags != null) { + feature.getProperties().getXyzNamespace().removeTags(removeTags, true); + } + } } From ec19663306a77625c218211a3f6a007f261f594c Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 28 Nov 2023 16:42:49 +0100 Subject: [PATCH 30/31] reformat --- .../app/service/UpdateFeatureTestHelper.java | 566 +++++++++--------- 1 file changed, 284 insertions(+), 282 deletions(-) diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java index cb991085b..aa9262625 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/UpdateFeatureTestHelper.java @@ -26,9 +26,11 @@ import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; import com.here.naksha.lib.core.models.geojson.implementation.XyzProperties; import com.here.naksha.lib.core.models.naksha.Space; + import java.net.http.HttpResponse; import java.util.List; import java.util.UUID; + import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.skyscreamer.jsonassert.JSONAssert; @@ -37,287 +39,287 @@ public class UpdateFeatureTestHelper { - final @NotNull NakshaApp app; - final @NotNull NakshaTestWebClient nakshaClient; - - public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { - this.app = app; - this.nakshaClient = nakshaClient; - } - - void tc0500_testUpdateFeatures() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Preparation: create storage, event handler, space, and initial features - final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); - nakshaClient.post("hub/storages", storage, streamId); - final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); - nakshaClient.post("hub/handlers", handler, streamId); - final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); - nakshaClient.post("hub/spaces", spaceJson, streamId); - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String createFeaturesJson = loadFileOrFail("TC0500_updateFeatures/create_features.json"); - nakshaClient.post("hub/spaces/" + space.getId() + "/features", createFeaturesJson, streamId); - // Read request body - final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); - // TODO: include geometry after Cursor-related changes -> - final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0501_testUpdateFeatureById() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = bodyJson; - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-feature-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature response body doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0502_testUpdateFeatureByWrongUriId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(404, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - - // Read request body - final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); - final String streamId = UUID.randomUUID().toString(); - - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-feature-1", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(400, response.statusCode(), "ResCode mismatch"); - JSONAssert.assertEquals( - "Update Feature error response doesn't match", - expectedBodyPart, - response.body(), - JSONCompareMode.LENIENT); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - } - - void tc0504_testUpdateFeaturesNoId() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // Read request body - final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - // When: Create Features request is submitted to NakshaHub Space Storage instance - final HttpResponse response = - nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); - - // Then: Perform assertions - assertEquals(200, response.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); - - final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(responseFeatureCollection); - final List inserted = responseFeatureCollection.getInserted(); - final List insertedFeatures = responseFeatureCollection.getFeatures(); - JSONAssert.assertEquals( - "{inserted:[" + inserted.size() + "]}", - response.body(), - new ArraySizeComparator(JSONCompareMode.LENIENT)); - - for (int i = 0; i < inserted.size(); i++) { - assertEquals( - inserted.get(i), - insertedFeatures.get(i).getId(), - "Mismatch between inserted v/s feature ID in the response at idx : " + i); - assertNotNull( - insertedFeatures.get(i).getProperties().getXyzNamespace().getUuid(), - "UUID found missing in response for feature at idx : " + i); + final @NotNull NakshaApp app; + final @NotNull NakshaTestWebClient nakshaClient; + + public UpdateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { + this.app = app; + this.nakshaClient = nakshaClient; + } + + void tc0500_testUpdateFeatures() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Preparation: create storage, event handler, space, and initial features + final String storage = loadFileOrFail("TC0500_updateFeatures/create_storage.json"); + nakshaClient.post("hub/storages", storage, streamId); + final String handler = loadFileOrFail("TC0500_updateFeatures/create_handler.json"); + nakshaClient.post("hub/handlers", handler, streamId); + final String spaceJson = loadFileOrFail("TC0500_updateFeatures/create_space.json"); + nakshaClient.post("hub/spaces", spaceJson, streamId); + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String createFeaturesJson = loadFileOrFail("TC0500_updateFeatures/create_features.json"); + nakshaClient.post("hub/spaces/" + space.getId() + "/features", createFeaturesJson, streamId); + // Read request body + final String bodyJson = loadFileOrFail("TC0500_updateFeatures/update_request.json"); + // TODO: include geometry after Cursor-related changes -> + final String expectedBodyPart = loadFileOrFail("TC0500_updateFeatures/response_no_geometry.json"); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0501_testUpdateFeatureById() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0501_updateOneFeatureById/update_request_and_response.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = bodyJson; + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-feature-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature response body doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0502_testUpdateFeatureByWrongUriId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/wrong-id", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(404, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0503_testUpdateFeatureWithMismatchingId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + + // Read request body + final String bodyJson = loadFileOrFail("TC0502_updateFeatureWithWrongUriId/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + final String expectedBodyPart = loadFileOrFail("TC0503_updateFeatureMismatchingId/response.json"); + final String streamId = UUID.randomUUID().toString(); + + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features/my-custom-feature-1", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(400, response.statusCode(), "ResCode mismatch"); + JSONAssert.assertEquals( + "Update Feature error response doesn't match", + expectedBodyPart, + response.body(), + JSONCompareMode.LENIENT); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + } + + void tc0504_testUpdateFeaturesNoId() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // Read request body + final String bodyJson = loadFileOrFail("TC0504_updateFeaturesNoIds/request.json"); + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + // When: Create Features request is submitted to NakshaHub Space Storage instance + final HttpResponse response = + nakshaClient.put("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); + + // Then: Perform assertions + assertEquals(200, response.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(response, HDR_STREAM_ID), "StreamId mismatch"); + + final XyzFeatureCollection responseFeatureCollection = parseJson(response.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final List inserted = responseFeatureCollection.getInserted(); + final List insertedFeatures = responseFeatureCollection.getFeatures(); + JSONAssert.assertEquals( + "{inserted:[" + inserted.size() + "]}", + response.body(), + new ArraySizeComparator(JSONCompareMode.LENIENT)); + + for (int i = 0; i < inserted.size(); i++) { + assertEquals( + inserted.get(i), + insertedFeatures.get(i).getId(), + "Mismatch between inserted v/s feature ID in the response at idx : " + i); + assertNotNull( + insertedFeatures.get(i).getProperties().getXyzNamespace().getUuid(), + "UUID found missing in response for feature at idx : " + i); + } + } + + void tc0505_testUpdateFeaturesWithUuid() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features + final String streamId = UUID.randomUUID().toString(); + + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + + final HttpResponse getResponse = + nakshaClient.get("hub/spaces/" + space.getId() + "/features/my-custom-id-301-2", streamId); + final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); + Assertions.assertNotNull(feature); + final XyzProperties newPropsOldUuid = feature.getProperties(); + final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); + final XyzProperties nullUuidProps = new XyzProperties(); + // Old UUID + newPropsOldUuid.put("speedLimit", "30"); + // New UUID + newPropsOutdatedUuid.put("speedLimit", "120"); + // Null UUID + nullUuidProps.put("uuid", null); + nullUuidProps.put("overriden", "yesyesyes"); + + // Execute request, correct UUID, should success + feature.setProperties(newPropsOldUuid); + final HttpResponse responseUpdateSuccess = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ + { + "type": "FeatureCollection", + "features": [ + """ + feature + "]}", + streamId); + + // Perform first assertions + assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); + final XyzFeatureCollection responseFeatureCollection = + parseJson(responseUpdateSuccess.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(responseFeatureCollection); + final XyzFeature updatedFeature = + responseFeatureCollection.getFeatures().get(0); + Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); + + // Execute request, outdated UUID, should fail + feature.setProperties(newPropsOutdatedUuid); + final HttpResponse responseUpdateFail = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ + { + "type": "FeatureCollection", + "features": [ + """ + feature + "]}", + streamId); + + // Perform second assertions + assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); + + // Execute request, null UUID, should success with overriding + feature.setProperties(nullUuidProps); + final HttpResponse responseOverriding = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features", + """ + { + "type": "FeatureCollection", + "features": [ + """ + feature + "]}", + streamId); + + // Perform third assertions + assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); + final XyzFeatureCollection featureCollection = parseJson(responseOverriding.body(), XyzFeatureCollection.class); + Assertions.assertNotNull(featureCollection); + final XyzFeature overridenFeature = featureCollection.getFeatures().get(0); + Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); + // Old properties like speedLimit should no longer be available + // The feature has been completely overwritten by the PUT request with null UUID + Assertions.assertFalse(overridenFeature.getProperties().containsKey("speedLimit")); + } + + void tc0506_testUpdateFeatureWithUuid() throws Exception { + // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} + final String streamId = UUID.randomUUID().toString(); + + // TODO: include geometry after Cursor-related changes -> + final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); + + final HttpResponse getResponse = + nakshaClient.get("hub/spaces/" + space.getId() + "/features/newly-inserted", streamId); + final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); + Assertions.assertNotNull(feature); + final XyzProperties newPropsOldUuid = feature.getProperties(); + final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); + final XyzProperties nullUuidProps = new XyzProperties(); + // Old UUID + newPropsOldUuid.put("speedLimit", "30"); + // New UUID + newPropsOutdatedUuid.put("speedLimit", "120"); + // Null UUID + nullUuidProps.put("uuid", null); + nullUuidProps.put("overriden", "yesyesyes"); + + // Execute request, correct UUID, should success + feature.setProperties(newPropsOldUuid); + final HttpResponse responseUpdateSuccess = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform first assertions + assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); + assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); + final XyzFeature updatedFeature = parseJson(responseUpdateSuccess.body(), XyzFeature.class); + Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); + Assertions.assertEquals("no", updatedFeature.getProperties().get("isImportant")); + + // Execute request, outdated UUID, should fail + feature.setProperties(newPropsOutdatedUuid); + final HttpResponse responseUpdateFail = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform second assertions + assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); + + // Execute request, null UUID, should success with overriding + feature.setProperties(nullUuidProps); + final HttpResponse responseOverriding = nakshaClient.put( + "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); + + // Perform third assertions + assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); + final XyzFeature overridenFeature = parseJson(responseOverriding.body(), XyzFeature.class); + Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); + // Old properties like isImportant should no longer be available + // The feature has been completely overwritten by the PUT request with null UUID + Assertions.assertFalse(overridenFeature.getProperties().containsKey("isImportant")); } - } - - void tc0505_testUpdateFeaturesWithUuid() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features - final String streamId = UUID.randomUUID().toString(); - - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - - final HttpResponse getResponse = - nakshaClient.get("hub/spaces/" + space.getId() + "/features/my-custom-id-301-2", streamId); - final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); - Assertions.assertNotNull(feature); - final XyzProperties newPropsOldUuid = feature.getProperties(); - final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); - final XyzProperties nullUuidProps = new XyzProperties(); - // Old UUID - newPropsOldUuid.put("speedLimit", "30"); - // New UUID - newPropsOutdatedUuid.put("speedLimit", "120"); - // Null UUID - nullUuidProps.put("uuid", null); - nullUuidProps.put("overriden", "yesyesyes"); - - // Execute request, correct UUID, should success - feature.setProperties(newPropsOldUuid); - final HttpResponse responseUpdateSuccess = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ -{ -"type": "FeatureCollection", -"features": [ -""" + feature + "]}", - streamId); - - // Perform first assertions - assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); - final XyzFeatureCollection responseFeatureCollection = - parseJson(responseUpdateSuccess.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(responseFeatureCollection); - final XyzFeature updatedFeature = - responseFeatureCollection.getFeatures().get(0); - Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); - - // Execute request, outdated UUID, should fail - feature.setProperties(newPropsOutdatedUuid); - final HttpResponse responseUpdateFail = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ -{ -"type": "FeatureCollection", -"features": [ -""" + feature + "]}", - streamId); - - // Perform second assertions - assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); - - // Execute request, null UUID, should success with overriding - feature.setProperties(nullUuidProps); - final HttpResponse responseOverriding = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features", - """ -{ -"type": "FeatureCollection", -"features": [ -""" + feature + "]}", - streamId); - - // Perform third assertions - assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); - final XyzFeatureCollection featureCollection = parseJson(responseOverriding.body(), XyzFeatureCollection.class); - Assertions.assertNotNull(featureCollection); - final XyzFeature overridenFeature = featureCollection.getFeatures().get(0); - Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); - // Old properties like speedLimit should no longer be available - // The feature has been completely overwritten by the PUT request with null UUID - Assertions.assertFalse(overridenFeature.getProperties().containsKey("speedLimit")); - } - - void tc0506_testUpdateFeatureWithUuid() throws Exception { - // Test API : PUT /hub/spaces/{spaceId}/features/{featureId} - final String streamId = UUID.randomUUID().toString(); - - // TODO: include geometry after Cursor-related changes -> - final Space space = parseJsonFileOrFail("TC0500_updateFeatures/create_space.json", Space.class); - - final HttpResponse getResponse = - nakshaClient.get("hub/spaces/" + space.getId() + "/features/newly-inserted", streamId); - final XyzFeature feature = parseJson(getResponse.body(), XyzFeature.class); - Assertions.assertNotNull(feature); - final XyzProperties newPropsOldUuid = feature.getProperties(); - final XyzProperties newPropsOutdatedUuid = newPropsOldUuid.deepClone(); - final XyzProperties nullUuidProps = new XyzProperties(); - // Old UUID - newPropsOldUuid.put("speedLimit", "30"); - // New UUID - newPropsOutdatedUuid.put("speedLimit", "120"); - // Null UUID - nullUuidProps.put("uuid", null); - nullUuidProps.put("overriden", "yesyesyes"); - - // Execute request, correct UUID, should success - feature.setProperties(newPropsOldUuid); - final HttpResponse responseUpdateSuccess = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); - - // Perform first assertions - assertEquals(200, responseUpdateSuccess.statusCode(), "ResCode mismatch"); - assertEquals(streamId, getHeader(responseUpdateSuccess, HDR_STREAM_ID), "StreamId mismatch"); - final XyzFeature updatedFeature = parseJson(responseUpdateSuccess.body(), XyzFeature.class); - Assertions.assertEquals("30", updatedFeature.getProperties().get("speedLimit")); - Assertions.assertEquals("no", updatedFeature.getProperties().get("isImportant")); - - // Execute request, outdated UUID, should fail - feature.setProperties(newPropsOutdatedUuid); - final HttpResponse responseUpdateFail = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); - - // Perform second assertions - assertEquals(409, responseUpdateFail.statusCode(), "ResCode mismatch"); - - // Execute request, null UUID, should success with overriding - feature.setProperties(nullUuidProps); - final HttpResponse responseOverriding = nakshaClient.put( - "hub/spaces/" + space.getId() + "/features/newly-inserted", feature.toString(), streamId); - - // Perform third assertions - assertEquals(200, responseOverriding.statusCode(), "ResCode mismatch"); - final XyzFeature overridenFeature = parseJson(responseOverriding.body(), XyzFeature.class); - Assertions.assertEquals("yesyesyes", overridenFeature.getProperties().get("overriden")); - // Old properties like isImportant should no longer be available - // The feature has been completely overwritten by the PUT request with null UUID - Assertions.assertFalse(overridenFeature.getProperties().containsKey("isImportant")); - } } From 1a726b7148e17975be754f48653ff490b1fc5f1f Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 28 Nov 2023 16:48:55 +0100 Subject: [PATCH 31/31] adjust description in openapi yaml --- .../src/main/resources/swagger/openapi.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/here-naksha-app-service/src/main/resources/swagger/openapi.yaml b/here-naksha-app-service/src/main/resources/swagger/openapi.yaml index 8697a0c98..c53c623b6 100644 --- a/here-naksha-app-service/src/main/resources/swagger/openapi.yaml +++ b/here-naksha-app-service/src/main/resources/swagger/openapi.yaml @@ -489,7 +489,9 @@ paths: description: > This method allows to upsert features in the storage associated (directly or via event handler) with the space. If a feature does not exist yet, it is created, if it is already available, it will be updated. - For each existing feature, if UUID is specified and matches the current UUID stored in Naksha, the feature will be updated. + For each existing feature, if UUID is not provided OR if provided UUID matches with what is stored in Naksha, the feature will be updated. + Recommendation is to always provide correct UUID to avoid inconsistency due to concurrency. + In the JSON feature content, UUID is a string located at "properties -> @ns:com:here:xyz -> uuid". If the UUID does not match, HTTP code 409 will be returned. It ensures an atomic operation, so either all features will be updated or none (in case of failure). operationId: putFeatures