diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 26ffa8f5a..eafb7e87a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,24 +1,68 @@ -name: Run XYZ Hub tests +name: Run Naksha tests -on: [push, pull_request, workflow_dispatch] +on: + pull_request: + types: + - opened # run whenever PR is opened + - synchronize # run whenever PR is updated + - reopened # run whenever PR is reopoened + workflow_dispatch: # manual run + + +permissions: + checks: write # for junit reporting + pull-requests: write # for jacoco PR comments + +env: + MIN_COVERAGE_OVERALL: 25 + MIN_COVERAGE_CHANGED_FILES: 65 jobs: test: runs-on: ubuntu-latest + services: + postgres: + image: postgis/postgis # Postgres with PostGIS extension + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 steps: - - uses: actions/checkout@v2 - - name: Cache local Maven repository - uses: actions/cache@v2 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- + - uses: actions/checkout@v4 - name: Setup Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + with: + gradle-version: 8.2 + - name: Execute tests & verify overall coverage + run: gradle test jacocoTestReport jacocoTestCoverageVerification + - name: Publish Test Report + uses: mikepenz/action-junit-report@v4 + if: success() || failure() # always run even if the previous step fails + with: + report_paths: '**/build/test-results/test/TEST-*.xml' + - name: Publish code coverage report as PR comment + id: jacoco + uses: madrapps/jacoco-report@v1.6.1 with: - java-version: '8' - - name: Start the XYZ Hub stack - run: mvn clean install -Pdocker -DskipTests=true -DdockerComposeFile=docker-compose-dynamodb.yml - - name: Run tests - run: mvn verify -DskipTests=false + paths: '**/build/reports/jacoco/test/jacocoTestReport.xml' + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: $MIN_COVERAGE_OVERALL + min-coverage-changed-files: $MIN_COVERAGE_CHANGED_FILES + title: Code Coverage + - name: Fail when coverage of changed files is too low + run: | + CHANGED_FILES_FAILED=$(echo '${{ steps.jacoco.outputs.coverage-changed-files }} < ${{ env.MIN_COVERAGE_CHANGED_FILES }}' | bc) + [[ $CHANGED_FILES_FAILED -ne 0 ]] && echo 'Changed files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}% is smaller than required ${{ env.MIN_COVERAGE_CHANGED_FILES }}%' + [[ $CHANGED_FILES_FAILED -ne 0 ]] && exit 1 || exit 0 \ No newline at end of file diff --git a/README.md b/README.md index 7a831e387..4ba34a47d 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,25 @@ The OpenAPI specification files are accessible under the following URIs: Currently to access the Swagger UI from a browser, use this URI [http://{host}:{port}/hub/static/index.html](http://localhost:8080/hub/static/index.html) +# Testing locally + +To run tests locally run Gradle `test` task: +```bash +./gradlew test +``` + +Code coverage report is generated with use of [jacoco](https://www.jacoco.org/) +To generate coverage use Gradle task `jacocoTestReport`: +```bash +./gradlew test jacocoTestReport +``` +Outputs for each subproject will be stored in `/[module]/build/reports/jacoco/test/html/index.html` + +To validate test coverage, run `jacocoTestCoverageVerification` Gradle task: +```bash +./gradlew test jacocoTestReport jacocoTestCoverageVerification +``` + # Acknowledgements XYZ Hub uses: diff --git a/build.gradle.kts b/build.gradle.kts index 7767d7b89..24890e3f6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,10 @@ @file:Suppress("PropertyName") +import org.gradle.api.tasks.testing.logging.TestExceptionFormat import java.net.URI import java.time.LocalDateTime import java.time.format.DateTimeFormatter +import org.gradle.kotlin.dsl.KotlinClosure2 repositories { maven { @@ -21,6 +23,8 @@ plugins { id("com.github.johnrengelman.shadow") version "8.1.1" // Don't apply for all projects, we individually only apply where Kotlin is used. kotlin("jvm") version "1.8.21" apply false + // overall code coverage + jacoco } group = "com.here.naksha" @@ -28,15 +32,16 @@ version = rootProject.properties["version"] as String val jetbrains_annotations = "org.jetbrains:annotations:24.0.1" -val vertx_core = "io.vertx:vertx-core:4.4.4" -val vertx_config = "io.vertx:vertx-config:4.4.4" -val vertx_auth_jwt = "io.vertx:vertx-auth-jwt:4.4.4" -val vertx_redis_client = "io.vertx:vertx-redis-client:4.4.4" -val vertx_jdbc_client = "io.vertx:vertx-jdbc-client:4.4.4" -val vertx_web = "io.vertx:vertx-web:4.4.4" -val vertx_web_openapi = "io.vertx:vertx-web-openapi:4.4.4" -val vertx_web_client = "io.vertx:vertx-web-client:4.4.4" -val vertx_web_templ = "io.vertx:vertx-web-templ-handlebars:4.4.4" +val vertx_version = "4.5.0" +val vertx_core = "io.vertx:vertx-core:$vertx_version" +val vertx_config = "io.vertx:vertx-config:$vertx_version" +val vertx_auth_jwt = "io.vertx:vertx-auth-jwt:$vertx_version" +val vertx_redis_client = "io.vertx:vertx-redis-client:$vertx_version" +val vertx_jdbc_client = "io.vertx:vertx-jdbc-client:$vertx_version" +val vertx_web = "io.vertx:vertx-web:$vertx_version" +val vertx_web_openapi = "io.vertx:vertx-web-openapi:$vertx_version" +val vertx_web_client = "io.vertx:vertx-web-client:$vertx_version" +val vertx_web_templ = "io.vertx:vertx-web-templ-handlebars:$vertx_version" val netty_transport_native_kqueue = "io.netty:netty-transport-native-kqueue:4.1.90.Final" val netty_transport_native_epoll = "io.netty:netty-transport-native-epoll:4.1.90.Final" @@ -102,11 +107,19 @@ val mockito = "org.mockito:mockito-core:3.12.4" val flipkart_zjsonpatch = "com.flipkart.zjsonpatch:zjsonpatch:0.4.13" val json_assert = "org.skyscreamer:jsonassert:1.5.1" +val resillience4j_retry = "io.github.resilience4j:resilience4j-retry:2.0.0" val mavenUrl = rootProject.properties["mavenUrl"] as String val mavenUser = rootProject.properties["mavenUser"] as String val mavenPassword = rootProject.properties["mavenPassword"] as String +/* + Overall coverage of subproject - it might be different for different subprojects + Configurable per project - see `setOverallCoverage` + */ +val minOverallCoverageKey: String = "minOverallCoverage" +val defaultOverallMinCoverage: Double = 0.8 // Don't decrease me! + /* IMPORTANT: api vs implementation @@ -128,6 +141,7 @@ subprojects { apply(plugin = "java") apply(plugin = "com.diffplug.spotless") apply(plugin = "java-library") + apply(plugin = "jacoco") repositories { maven(uri("https://repo.osgeo.org/repository/release/")) @@ -174,7 +188,17 @@ subprojects { test { maxHeapSize = "4g" useJUnitPlatform() - testLogging.showStandardStreams = true + testLogging { + showStandardStreams = true + exceptionFormat = TestExceptionFormat.FULL + events("standardOut", "started", "passed", "skipped", "failed") + } + afterTest(KotlinClosure2( + { descriptor: TestDescriptor, result: TestResult -> + val totalTime = result.endTime - result.startTime + println("Total time of $descriptor.name was $totalTime") + } + )) } compileJava { @@ -189,6 +213,24 @@ subprojects { addStringOption("Xmaxwarns", "1") } } + + jacocoTestReport { + dependsOn(test) + reports { + xml.required = true + } + } + + jacocoTestCoverageVerification { + dependsOn(jacocoTestReport) + violationRules { + rule { + limit { + minimum = getOverallCoverage().toBigDecimal() + } + } + } + } } java { @@ -257,6 +299,7 @@ project(":here-naksha-lib-core") { implementation(vividsolutions_jts_core) implementation(google_flatbuffers) } + setOverallCoverage(0.3) // only increasing allowed! } project(":here-naksha-lib-heapcache") { @@ -270,6 +313,7 @@ project(":here-naksha-lib-heapcache") { testImplementation(mockito) implementation(vividsolutions_jts_core) } + setOverallCoverage(0.5) // only increasing allowed! } project(":here-naksha-lib-psql") { @@ -290,6 +334,7 @@ project(":here-naksha-lib-psql") { testImplementation(mockito) testImplementation(spatial4j) } + setOverallCoverage(0.0) // only increasing allowed! } /* @@ -298,6 +343,7 @@ project(":here-naksha-lib-extension") { dependencies { api(project(":here-naksha-lib-core")) } + setOverallCoverage(0.4) // only increasing allowed! } */ @@ -311,6 +357,7 @@ project(":here-naksha-handler-activitylog") { implementation(flipkart_zjsonpatch) testImplementation(jayway_jsonpath) } + setOverallCoverage(0.4) // only increasing allowed! } */ @@ -392,6 +439,7 @@ project(":here-naksha-lib-handlers") { testImplementation(json_assert) testImplementation(mockito) } + setOverallCoverage(0.3) // only increasing allowed! } //} catch (ignore: UnknownProjectException) { //} @@ -416,7 +464,9 @@ project(":here-naksha-lib-handlers") { implementation(vertx_web_openapi) testImplementation(json_assert) + testImplementation(resillience4j_retry) } + setOverallCoverage(0.25) // only increasing allowed! } //} catch (ignore: UnknownProjectException) { //} @@ -469,3 +519,17 @@ rootProject.tasks.shadowJar { attributes["Main-Class"] = "com.here.naksha.app.service.NakshaApp" } } + + +fun Project.setOverallCoverage(minOverallCoverage: Double) { + ext.set(minOverallCoverageKey, minOverallCoverage) +} + +fun Project.getOverallCoverage(): Double { + return if (ext.has(minOverallCoverageKey)) { + ext.get(minOverallCoverageKey) as? Double + ?: throw IllegalStateException("Property '$minOverallCoverageKey' is expected to be Double") + } else { + defaultOverallMinCoverage + } +} 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 a4e99b2b8..83fc29980 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 @@ -42,7 +42,13 @@ import com.here.naksha.app.service.AbstractNakshaHubVerticle; import com.here.naksha.app.service.NakshaApp; -import com.here.naksha.app.service.http.apis.*; +import com.here.naksha.app.service.http.apis.Api; +import com.here.naksha.app.service.http.apis.EventHandlerApi; +import com.here.naksha.app.service.http.apis.HealthApi; +import com.here.naksha.app.service.http.apis.ReadFeatureApi; +import com.here.naksha.app.service.http.apis.SpaceApi; +import com.here.naksha.app.service.http.apis.StorageApi; +import com.here.naksha.app.service.http.apis.WriteFeatureApi; import com.here.naksha.app.service.http.auth.NakshaJwtAuthHandler; import com.here.naksha.lib.core.AbstractTask; import com.here.naksha.lib.core.INaksha; @@ -60,9 +66,11 @@ import com.here.naksha.lib.core.util.MIMEType; import com.here.naksha.lib.hub.NakshaHubConfig; import io.netty.handler.codec.http.HttpResponseStatus; +import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpConnection; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerResponse; @@ -216,21 +224,24 @@ public void start(final @NotNull Promise startPromise) { // round-robin strategy. // // https://vertx.io/docs/vertx-core/java/#_server_sharing - vertx.createHttpServer(SERVER_OPTIONS).requestHandler(router).listen(hubConfig.httpPort, result -> { - if (result.succeeded()) { - log.atInfo() - .setMessage("HTTP Server started on port {}") - .addArgument(hubConfig.httpPort) - .log(); - startPromise.complete(); - } else { - log.atError() - .setMessage("An error occurred, during the initialization of the server.") - .setCause(result.cause()) - .log(); - startPromise.fail(result.cause()); - } - }); + vertx.createHttpServer(SERVER_OPTIONS) + .requestHandler(router) + .connectionHandler(loggingConnectionHandler()) + .listen(hubConfig.httpPort, result -> { + if (result.succeeded()) { + log.atInfo() + .setMessage("HTTP Server started on port {}") + .addArgument(hubConfig.httpPort) + .log(); + startPromise.complete(); + } else { + log.atError() + .setMessage("An error occurred, during the initialization of the server.") + .setCause(result.cause()) + .log(); + startPromise.fail(result.cause()); + } + }); } catch (Throwable t) { log.atError() .setMessage( @@ -327,7 +338,8 @@ 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. @@ -396,6 +408,15 @@ private void onNewRequest(final @NotNull RoutingContext routingContext) { routingContext.next(); } + /** + * @return simple http connection handler that logs closing and failing connections + */ + private Handler loggingConnectionHandler() { + return httpConnection -> httpConnection + .closeHandler(v -> log.info("Closing connection")) + .exceptionHandler(t -> log.info("Connection exception", t)); + } + private static final Pattern FATAL_ERROR_MSG_PATTERN = Pattern.compile("^[0-9a-zA-Z.-_\\-]+$"); /** @@ -553,7 +574,7 @@ public void sendErrorResponse(@NotNull RoutingContext routingContext, @NotNull T /** * Prepare XyzFeatureCollection response by extracting feature results from ModifyFeaturesResp object * - * @param modifyResponse The object to extract feature results from + * @param modifyResponse The object to extract feature results from * @return The XyzFeatureCollection response containing list of inserted/updated/delete features */ public XyzFeatureCollection transformModifyResponse(@NotNull ModifyFeaturesResp modifyResponse) { diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/common/NakshaTestWebClient.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/common/NakshaTestWebClient.java new file mode 100644 index 000000000..40cda2743 --- /dev/null +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/common/NakshaTestWebClient.java @@ -0,0 +1,129 @@ +/* + * 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.common; + +import static com.here.naksha.app.common.TestUtil.HDR_STREAM_ID; +import static java.net.http.HttpClient.Version.HTTP_1_1; + +import io.github.resilience4j.core.functions.CheckedFunction; +import io.github.resilience4j.retry.Retry; +import io.github.resilience4j.retry.RetryConfig; +import io.github.resilience4j.retry.RetryRegistry; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Version; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; +import java.net.http.HttpTimeoutException; +import java.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NakshaTestWebClient { + + private static final Logger logger = LoggerFactory.getLogger(NakshaTestWebClient.class); + private static final String NAKSHA_HTTP_URI = "http://localhost:8080/"; + private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10); + private static final Duration SOCKET_TIMEOUT = Duration.ofSeconds(2); + + private final HttpClient httpClient; + + private final RetryRegistry retryRegistry; + + public NakshaTestWebClient() { + httpClient = HttpClient.newBuilder() + .version(HTTP_1_1) + .connectTimeout(CONNECT_TIMEOUT) + .build(); + retryRegistry = configureRetryRegistry(); + } + + public HttpResponse get(String subPath, String streamId) throws URISyntaxException { + HttpRequest getRequest = requestBuilder() + .uri(nakshaPath(subPath)) + .GET() + .header(HDR_STREAM_ID, streamId) + .build(); + return send(getRequest); + } + + public HttpResponse post(String subPath, String jsonBody, String streamId) throws URISyntaxException { + HttpRequest postRequest = requestBuilder() + .uri(nakshaPath(subPath)) + .POST(BodyPublishers.ofString(jsonBody)) + .header("Content-Type", "application/json") + .header(HDR_STREAM_ID, streamId) + .build(); + return send(postRequest); + } + + public HttpResponse put(String subPath, String jsonBody, String streamId) throws URISyntaxException { + HttpRequest putRequest = requestBuilder() + .uri(nakshaPath(subPath)) + .PUT(BodyPublishers.ofString(jsonBody)) + .header("Content-Type", "application/json") + .header(HDR_STREAM_ID, streamId) + .build(); + return send(putRequest); + } + + private HttpResponse send(HttpRequest request) { + String retryId = retryIdForRequest(request); + CheckedFunction> responseSupplier = + Retry.decorateCheckedFunction(retry(retryId), this::sendOnce); + try { + return responseSupplier.apply(request); + } catch (Throwable e) { + throw new RuntimeException("Applying retry (%s) failed".formatted(retryId), e); + } + } + + private HttpResponse sendOnce(HttpRequest request) throws IOException, InterruptedException { + return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + } + + private HttpRequest.Builder requestBuilder() { + return HttpRequest.newBuilder().version(Version.HTTP_1_1).timeout(SOCKET_TIMEOUT); + } + + private URI nakshaPath(String subPath) throws URISyntaxException { + return new URI(NAKSHA_HTTP_URI + subPath); + } + + private Retry retry(String name) { + Retry retry = retryRegistry.retry(name); + retry.getEventPublisher().onRetry(ev -> logger.info("Retry triggereed: {}", name)); + return retry; + } + + private static String retryIdForRequest(HttpRequest request) { + return "%s_%s_retry".formatted(request.method(), request.uri().toString()); + } + + private static RetryRegistry configureRetryRegistry() { + RetryConfig config = RetryConfig.custom() + .maxAttempts(3) + .retryExceptions(HttpTimeoutException.class) + .build(); + return RetryRegistry.of(config); + } +} diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/CreateFeatureTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/CreateFeatureTestHelper.java index 24220f7b6..b51ed7400 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/CreateFeatureTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/CreateFeatureTestHelper.java @@ -18,19 +18,22 @@ */ 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 com.here.naksha.app.common.TestUtil.getHeader; import static com.here.naksha.app.common.TestUtil.loadFileOrFail; +import static com.here.naksha.app.common.TestUtil.parseJson; +import static com.here.naksha.app.common.TestUtil.parseJsonFileOrFail; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.here.naksha.app.common.NakshaTestWebClient; import com.here.naksha.app.service.models.FeatureCollectionRequest; 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.naksha.Space; -import com.here.naksha.lib.core.models.storage.*; -import java.net.URI; import java.net.URLEncoder; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; @@ -45,19 +48,11 @@ public class CreateFeatureTestHelper { final @NotNull NakshaApp app; - final @NotNull String nakshaHttpUri; - final @NotNull HttpClient httpClient; - final @NotNull HttpRequest stdHttpRequest; - - public CreateFeatureTestHelper( - final @NotNull NakshaApp app, - final @NotNull String nakshaHttpUri, - final @NotNull HttpClient httpClient, - final @NotNull HttpRequest stdHttpRequest) { + final @NotNull NakshaTestWebClient nakshaClient; + + public CreateFeatureTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { this.app = app; - this.nakshaHttpUri = nakshaHttpUri; - this.httpClient = httpClient; - this.stdHttpRequest = stdHttpRequest; + this.nakshaClient = nakshaClient; } private void standardAssertions( @@ -116,33 +111,18 @@ public void tc0300_testCreateFeaturesWithNewIds() throws Exception { // Given: Storage (mock implementation) configured in Admin storage final String storageJson = loadFileOrFail("TC0300_createFeaturesWithNewIds/create_storage.json"); streamId = UUID.randomUUID().toString(); - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(nakshaHttpUri + "hub/storages")) - .POST(HttpRequest.BodyPublishers.ofString(storageJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/storages", storageJson, streamId); assertEquals(200, response.statusCode(), "ResCode mismatch. Failed creating Storage"); // Given: EventHandler (uses above Storage) configured in Admin storage final String eventHandlerJson = loadFileOrFail("TC0300_createFeaturesWithNewIds/create_event_handler.json"); - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(nakshaHttpUri + "hub/handlers")) - .POST(HttpRequest.BodyPublishers.ofString(eventHandlerJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/handlers", eventHandlerJson, streamId); assertEquals(200, response.statusCode(), "ResCode mismatch. Failed creating Event Handler"); // Given: Space (uses above EventHandler) configured in Admin storage final Space space = parseJsonFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json", Space.class); final String spaceJsonString = loadFileOrFail("TC0300_createFeaturesWithNewIds/create_space.json"); - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(nakshaHttpUri + "hub/spaces")) - .POST(HttpRequest.BodyPublishers.ofString(spaceJsonString)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces", spaceJsonString, streamId); assertEquals(200, response.statusCode(), "ResCode mismatch. Failed creating Event Handler"); // Given: Create Features request (against above Space) @@ -154,12 +134,7 @@ public void tc0300_testCreateFeaturesWithNewIds() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + space.getId() + "/features")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); // Then: Perform assertions standardAssertions(response, 200, expectedBodyPart, streamId); @@ -188,12 +163,7 @@ public void tc0301_testCreateFeaturesWithGivenIds() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces/" + spaceId + "/features", bodyJson, streamId); // Then: Perform assertions standardAssertions(response, 200, expectedBodyPart, streamId); @@ -224,13 +194,8 @@ public void tc0302_testCreateFeaturesWithPrefixId() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features?prefixId=" - + URLEncoder.encode(prefixId, UTF_8))) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post( + "hub/spaces/" + spaceId + "/features?prefixId=" + utf8Encoded(prefixId), bodyJson, streamId); // Then: Perform assertions standardAssertions(response, 200, expectedBodyPart, streamId); @@ -252,9 +217,9 @@ public void tc0303_testCreateFeaturesWithAddTags() throws Exception { final String spaceId = "um-mod-topology-dev"; // Given: addTags API query param final String tagQueryParam = "addTags=New_Normalized_Tag" - + "&addTags=" + URLEncoder.encode("@New_Non_Normalized_Tag", UTF_8) + + "&addTags=" + utf8Encoded("@New_Non_Normalized_Tag") + "&addTags=Existing_Normalized_Tag" - + "&addTags=" + URLEncoder.encode("@Existing_Non_Normalized_Tag", UTF_8); + + "&addTags=" + utf8Encoded("@Existing_Non_Normalized_Tag"); // Given: Create Features request final String bodyJson = loadFileOrFail("TC0303_createFeaturesWithAddTags/create_features.json"); // TODO: include geometry after Cursor-related changes -> @@ -264,12 +229,7 @@ public void tc0303_testCreateFeaturesWithAddTags() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features?" + tagQueryParam)) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces/" + spaceId + "/features?" + tagQueryParam, bodyJson, streamId); // Then: Perform assertions standardAssertions(response, 200, expectedBodyPart, streamId); @@ -302,12 +262,7 @@ public void tc0304_testCreateFeaturesWithRemoveTags() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features?" + tagQueryParam)) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces/" + spaceId + "/features?" + tagQueryParam, bodyJson, streamId); // Then: Perform assertions standardAssertions(response, 200, expectedBodyPart, streamId); @@ -330,17 +285,12 @@ public void tc0305_testCreateFeaturesWithDupIds() throws Exception { // Given: New Features in place final String bodyJson = loadFileOrFail("TC0305_createFeaturesWithDupIds/create_features.json"); streamId = UUID.randomUUID().toString(); - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(nakshaHttpUri + "hub/spaces/" + spaceId + "/features")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces/" + spaceId + "/features", bodyJson, streamId); assertEquals(200, response.statusCode(), "ResCode mismatch"); // When: Create Features request is submitted with the same Ids final String expectedBodyPart = loadFileOrFail("TC0305_createFeaturesWithDupIds/feature_response_part.json"); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces/" + spaceId + "/features", bodyJson, streamId); // Then: Perform assertions standardAssertions(response, 409, expectedBodyPart, streamId); @@ -352,18 +302,10 @@ public void tc0307_testCreateFeaturesWithNoHandler() throws Exception { // Test API : POST /hub/spaces/{spaceId}/features // Validate request gets failed if we attempt creating features with no associated Event Handler String streamId = UUID.randomUUID().toString(); - ; - HttpRequest request; - HttpResponse response; // Given: Space (without EventHandler) configured in Admin storage final Space space = parseJsonFileOrFail("TC0307_createFeaturesWithNoHandler/create_space.json", Space.class); - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(nakshaHttpUri + "hub/spaces")) - .POST(HttpRequest.BodyPublishers.ofString(space.toString())) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = nakshaClient.post("hub/spaces", space.toString(), streamId); assertEquals(200, response.statusCode(), "ResCode mismatch. Failed creating Event Handler"); // Given: Create Features request (against above Space) @@ -371,12 +313,7 @@ public void tc0307_testCreateFeaturesWithNoHandler() throws Exception { final String expectedBodyPart = loadFileOrFail("TC0307_createFeaturesWithNoHandler/feature_response_part.json"); // When: Create Features request is submitted to NakshaHub Space Storage instance - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(nakshaHttpUri + "hub/spaces/" + space.getId() + "/features")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); // Then: Perform assertions standardAssertions(response, 404, expectedBodyPart, streamId); @@ -398,14 +335,13 @@ public void tc0308_testCreateFeaturesWithNoSpace() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces/" + spaceId + "/features", bodyJson, streamId); // Then: Perform assertions standardAssertions(response, 404, expectedBodyPart, streamId); } + + private String utf8Encoded(String text) { + return URLEncoder.encode(text, UTF_8); + } } 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..f08d5db87 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 @@ -22,17 +22,13 @@ 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 org.junit.jupiter.api.Assertions.assertEquals; +import com.here.naksha.app.common.NakshaTestWebClient; 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; @@ -46,12 +42,10 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class NakshaAppTest { - static NakshaApp app = null; - static NakshaHubConfig config = null; + static NakshaApp app; + static NakshaHubConfig config; - static final String NAKSHA_HTTP_URI = "http://localhost:8080/"; - static HttpClient httpClient; - static HttpRequest stdHttpRequest; + static NakshaTestWebClient nakshaClient; static CreateFeatureTestHelper createFeatureTests; static ReadFeaturesByIdsTestHelper readFeaturesByIdsTests; @@ -63,16 +57,11 @@ static void prepare() throws InterruptedException, URISyntaxException { app.start(); Thread.sleep(5000); // wait for server to come up // create standard Http client and request which will be used across tests - httpClient = - HttpClient.newBuilder().connectTimeout(Duration.of(10, SECONDS)).build(); - stdHttpRequest = HttpRequest.newBuilder() - .uri(new URI(NAKSHA_HTTP_URI)) - .header("Content-Type", "application/json") - .build(); + nakshaClient = new NakshaTestWebClient(); // create test helpers - createFeatureTests = new CreateFeatureTestHelper(app, NAKSHA_HTTP_URI, httpClient, stdHttpRequest); - readFeaturesByIdsTests = new ReadFeaturesByIdsTestHelper(app, NAKSHA_HTTP_URI, httpClient, stdHttpRequest); + createFeatureTests = new CreateFeatureTestHelper(app, nakshaClient); + readFeaturesByIdsTests = new ReadFeaturesByIdsTestHelper(app, nakshaClient); } @Test @@ -85,12 +74,7 @@ void tc0001_testCreateStorages() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.post("hub/storages", bodyJson, streamId); // 3. Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -109,12 +93,7 @@ void tc0002_testCreateDuplicateStorage() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.post("hub/storages", bodyJson, streamId); // 3. Perform assertions assertEquals(409, response.statusCode(), "ResCode mismatch"); @@ -133,12 +112,7 @@ void tc0003_testCreateStorageMissingClassName() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.post("hub/storages", bodyJson, streamId); // 3. Perform assertions assertEquals(400, response.statusCode(), "ResCode mismatch"); @@ -151,11 +125,8 @@ void tc0003_testCreateStorageMissingClassName() throws Exception { @Order(2) void tc0004_testInvalidUrlPath() throws Exception { // Test API : GET /hub/invalid_storages - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/invalid_storages")) - .GET() - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.get("hub/invalid_storages", UUID.randomUUID().toString()); // Perform assertions assertEquals(404, response.statusCode(), "ResCode mismatch"); @@ -170,12 +141,7 @@ void tc0020_testGetStorageById() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages/um-mod-dev")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.get("hub/storages/um-mod-dev", streamId); // 3. Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -192,12 +158,7 @@ void tc0021_testGetStorageByWrongId() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages/nothingness")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.get("hub/storages/nothingness", streamId); // 3. Perform assertions assertEquals(404, response.statusCode(), "ResCode mismatch"); @@ -213,12 +174,7 @@ void tc0040_testGetStorages() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.get("hub/storages", streamId); // 3. Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -237,12 +193,7 @@ void tc0060_testUpdateStorage() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages/um-mod-dev")) - .PUT(HttpRequest.BodyPublishers.ofString(updateStorageJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.put("hub/storages/um-mod-dev", updateStorageJson, streamId); // Then: assertEquals(200, response.statusCode()); @@ -260,12 +211,8 @@ void tc0061_testUpdateNonexistentStorage() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages/this-id-does-not-exist")) - .PUT(HttpRequest.BodyPublishers.ofString(updateStorageJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/storages/this-id-does-not-exist", updateStorageJson, streamId); // Then: assertEquals(409, response.statusCode()); @@ -283,12 +230,7 @@ void tc0062_testUpdateStorageWithoutClassName() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages/um-mod-dev")) - .PUT(HttpRequest.BodyPublishers.ofString(updateStorageJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.put("hub/storages/um-mod-dev", updateStorageJson, streamId); // Then: assertEquals(400, response.statusCode()); @@ -307,12 +249,8 @@ void tc0063_testUpdateStorageWithWithMismatchingId() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/storages/not-really-um-mod-dev")) - .PUT(HttpRequest.BodyPublishers.ofString(bodyWithDifferentStorageId)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/storages/not-really-um-mod-dev", bodyWithDifferentStorageId, streamId); // Then: assertEquals(400, response.statusCode()); @@ -325,17 +263,12 @@ void tc0063_testUpdateStorageWithWithMismatchingId() throws Exception { void tc0100_testCreateEventHandler() throws Exception { // Test API : POST /hub/handlers // 1. Load test data - final String bodyJson1 = loadFileOrFail("TC0100_createEventHandler/create_event_handler.json"); + final String bodyJson = loadFileOrFail("TC0100_createEventHandler/create_event_handler.json"); final String expectedCreationResponse = loadFileOrFail("TC0100_createEventHandler/response_create_1.json"); final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call creating handler - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/handlers")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson1)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.post("hub/handlers", bodyJson, streamId); // 3. Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -352,17 +285,14 @@ void tc0100_testCreateEventHandler() throws Exception { void tc0101_testDuplicateEventHandler() throws Exception { // Test API : POST /hub/handlers // 1. Load test data - final String bodyJson1 = loadFileOrFail("TC0100_createEventHandler/create_event_handler.json"); + final String bodyJson = loadFileOrFail("TC0100_createEventHandler/create_event_handler.json"); final String expectedDuplicateResponse = loadFileOrFail("TC0101_duplicateEventHandler/response_conflict.json"); final String streamId = UUID.randomUUID().toString(); + // 2. Perform REST API call creating handler - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/handlers")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson1)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - // 5. Perform assertions + final HttpResponse response = nakshaClient.post("hub/handlers", bodyJson, streamId); + + // 3. Perform assertions assertEquals(409, response.statusCode(), "ResCode mismatch"); JSONAssert.assertEquals( "Expecting duplicated handler in response", @@ -381,12 +311,7 @@ void tc0102_testCreateHandlerMissingClassName() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/handlers")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.post("hub/handlers", bodyJson, streamId); // 3. Perform assertions assertEquals(400, response.statusCode(), "ResCode mismatch"); @@ -404,12 +329,7 @@ void tc0120_testGetHandlerById() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/handlers/test-handler")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.get("hub/handlers/test-handler", streamId); // 3. Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -426,12 +346,7 @@ void tc0121_testGetHandlerByWrongId() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/handlers/not-real-handler")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.get("hub/handlers/not-real-handler", streamId); // 3. Perform assertions assertEquals(404, response.statusCode(), "ResCode mismatch"); @@ -447,12 +362,7 @@ void tc0140_testGetHandlers() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/handlers")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.get("hub/handlers", streamId); // 3. Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -471,12 +381,8 @@ void tc0160_testUpdateEventHandler() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/handlers/test-handler")) - .PUT(HttpRequest.BodyPublishers.ofString(updateEventHandlerJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/handlers/test-handler", updateEventHandlerJson, streamId); // Then: assertEquals(200, response.statusCode()); @@ -495,12 +401,8 @@ void tc0161_testUpdateNonexistentEventHandler() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/handlers/non-existent-test-handler")) - .PUT(HttpRequest.BodyPublishers.ofString(updateEventHandlerJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/handlers/non-existent-test-handler", updateEventHandlerJson, streamId); // Then: assertEquals(409, response.statusCode()); @@ -519,12 +421,9 @@ void tc0162_testUpdateEventHandlerWithMismatchingId() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/handlers/test-handler")) - .PUT(HttpRequest.BodyPublishers.ofString(updateOtherHandlerJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/handlers/test-handler", updateOtherHandlerJson, streamId); + // Then: assertEquals(400, response.statusCode()); JSONAssert.assertEquals(expectedErrorResponse, response.body(), JSONCompareMode.LENIENT); @@ -541,12 +440,7 @@ void tc0200_testCreateSpace() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces")) - .POST(HttpRequest.BodyPublishers.ofString(spaceJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.post("hub/spaces", spaceJson, streamId); // 3. Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -565,12 +459,7 @@ void tc0201_testCreateDuplicateSpace() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces")) - .POST(HttpRequest.BodyPublishers.ofString(duplicatedSpace)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.post("hub/spaces", duplicatedSpace, streamId); // 3. Perform assertions assertEquals(409, response.statusCode(), "ResCode mismatch"); @@ -586,13 +475,9 @@ void tc0220_testGetSpaceById() throws Exception { // 1. Load test data final String expectedBodyPart = loadFileOrFail("TC0200_createSpace/response.json"); final String streamId = UUID.randomUUID().toString(); + // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/test-space")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.get("hub/spaces/test-space", streamId); // 3. Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -608,12 +493,7 @@ void tc0221_testGetSpaceByWrongId() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/not-real-space")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.get("hub/spaces/not-real-space", streamId); // 3. Perform assertions assertEquals(404, response.statusCode(), "ResCode mismatch"); @@ -629,12 +509,7 @@ void tc0240_testGetSpaces() throws Exception { final String streamId = UUID.randomUUID().toString(); // 2. Perform REST API call - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.get("hub/spaces", streamId); // 3. Perform assertions assertEquals(200, response.statusCode(), "ResCode mismatch"); @@ -653,12 +528,7 @@ void tc0260_testUpdateSpace() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/test-space")) - .PUT(HttpRequest.BodyPublishers.ofString(updateStorageJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = nakshaClient.put("hub/spaces/test-space", updateStorageJson, streamId); // Then: assertEquals(200, response.statusCode()); @@ -676,12 +546,8 @@ void tc0261_testUpdateNonexistentSpace() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/non-existent-space")) - .PUT(HttpRequest.BodyPublishers.ofString(updateSpaceJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/spaces/non-existent-space", updateSpaceJson, streamId); // Then: assertEquals(409, response.statusCode()); @@ -699,12 +565,8 @@ void tc0263_testUpdateSpaceWithWithMismatchingId() throws Exception { final String streamId = UUID.randomUUID().toString(); // When: - final HttpRequest request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(NAKSHA_HTTP_URI + "hub/spaces/test-space")) - .PUT(HttpRequest.BodyPublishers.ofString(bodyWithDifferentSpaceId)) - .header(HDR_STREAM_ID, streamId) - .build(); - final HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = + nakshaClient.put("hub/spaces/test-space", bodyWithDifferentSpaceId, streamId); // Then: assertEquals(400, response.statusCode()); diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/ReadFeaturesByIdsTestHelper.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/ReadFeaturesByIdsTestHelper.java index 33dfe745a..268602768 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/ReadFeaturesByIdsTestHelper.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/ReadFeaturesByIdsTestHelper.java @@ -18,19 +18,29 @@ */ 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 com.here.naksha.app.common.TestUtil.getHeader; +import static com.here.naksha.app.common.TestUtil.loadFileOrFail; +import static com.here.naksha.app.common.TestUtil.newTestNakshaContext; +import static com.here.naksha.app.common.TestUtil.parseJson; +import static com.here.naksha.app.common.TestUtil.parseJsonFileOrFail; import static com.here.naksha.lib.core.util.storage.RequestHelper.createFeatureRequest; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.here.naksha.app.common.NakshaTestWebClient; import com.here.naksha.lib.core.NakshaAdminCollection; 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.naksha.EventHandler; import com.here.naksha.lib.core.models.naksha.Space; -import com.here.naksha.lib.core.models.storage.*; +import com.here.naksha.lib.core.models.storage.IfConflict; +import com.here.naksha.lib.core.models.storage.IfExists; +import com.here.naksha.lib.core.models.storage.Result; +import com.here.naksha.lib.core.models.storage.SuccessResult; +import com.here.naksha.lib.core.models.storage.WriteFeatures; import com.here.naksha.lib.core.storage.IWriteSession; -import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; @@ -43,19 +53,11 @@ public class ReadFeaturesByIdsTestHelper { final @NotNull NakshaApp app; - final @NotNull String nakshaHttpUri; - final @NotNull HttpClient httpClient; - final @NotNull HttpRequest stdHttpRequest; - - public ReadFeaturesByIdsTestHelper( - final @NotNull NakshaApp app, - final @NotNull String nakshaHttpUri, - final @NotNull HttpClient httpClient, - final @NotNull HttpRequest stdHttpRequest) { + final @NotNull NakshaTestWebClient nakshaClient; + + public ReadFeaturesByIdsTestHelper(final @NotNull NakshaApp app, final @NotNull NakshaTestWebClient nakshaClient) { this.app = app; - this.nakshaHttpUri = nakshaHttpUri; - this.httpClient = httpClient; - this.stdHttpRequest = stdHttpRequest; + this.nakshaClient = nakshaClient; } private void standardAssertions( @@ -95,12 +97,7 @@ public void tc0400_testReadFeaturesByIds() throws Exception { final String storageJson = loadFileOrFail("ReadFeatures/ByIds/TC0400_ExistingAndMissingIds/create_storage.json"); streamId = UUID.randomUUID().toString(); - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(nakshaHttpUri + "hub/storages")) - .POST(HttpRequest.BodyPublishers.ofString(storageJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/storages", storageJson, streamId); assertEquals(200, response.statusCode(), "ResCode mismatch. Failed creating Storage"); // Given: EventHandler (uses above Storage) configured in Admin storage @@ -130,12 +127,7 @@ public void tc0400_testReadFeaturesByIds() throws Exception { // Given: New Features persisted in above Space String bodyJson = loadFileOrFail("ReadFeatures/ByIds/TC0400_ExistingAndMissingIds/create_features.json"); streamId = UUID.randomUUID().toString(); - request = HttpRequest.newBuilder(stdHttpRequest, (k, v) -> true) - .uri(new URI(nakshaHttpUri + "hub/spaces/" + space.getId() + "/features")) - .POST(HttpRequest.BodyPublishers.ofString(bodyJson)) - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.post("hub/spaces/" + space.getId() + "/features", bodyJson, streamId); assertEquals(200, response.statusCode(), "ResCode mismatch. Failed creating new Features"); // Given: Features By Ids request (against above space) @@ -148,12 +140,7 @@ public void tc0400_testReadFeaturesByIds() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + space.getId() + "/features?" + idsQueryParam)) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.get("hub/spaces/" + space.getId() + "/features?" + idsQueryParam, streamId); // Then: Perform assertions standardAssertions(response, 200, expectedBodyPart, streamId); @@ -179,12 +166,7 @@ public void tc0401_testReadFeaturesForMissingIds() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features?" + idsQueryParam)) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.get("hub/spaces/" + spaceId + "/features?" + idsQueryParam, streamId); // Then: Perform assertions standardAssertions(response, 200, expectedBodyPart, streamId); @@ -206,12 +188,7 @@ public void tc0402_testReadFeaturesWithoutIds() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features")) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.get("hub/spaces/" + spaceId + "/features", streamId); // Then: Perform assertions standardAssertions(response, 400, expectedBodyPart, streamId); @@ -234,12 +211,7 @@ public void tc0403_testReadFeaturesByIdsFromMissingSpace() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features?" + idsQueryParam)) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.get("hub/spaces/" + spaceId + "/features?" + idsQueryParam, streamId); // Then: Perform assertions standardAssertions(response, 404, expectedBodyPart, streamId); @@ -264,12 +236,7 @@ public void tc0404_testReadFeatureById() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features/" + featureId)) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.get("hub/spaces/" + spaceId + "/features/" + featureId, streamId); // Then: Perform assertions standardAssertions(response, 200, expectedBodyPart, streamId); @@ -297,12 +264,7 @@ public void tc0405_testReadFeatureForMissingId() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features/" + featureId)) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.get("hub/spaces/" + spaceId + "/features/" + featureId, streamId); // Then: Perform assertions standardAssertions(response, 404, expectedBodyPart, streamId); @@ -325,12 +287,7 @@ public void tc0406_testReadFeatureByIdFromMissingSpace() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features/" + featureId)) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.get("hub/spaces/" + spaceId + "/features/" + featureId, streamId); // Then: Perform assertions standardAssertions(response, 404, expectedBodyPart, streamId); @@ -355,12 +312,7 @@ public void tc0407_testReadFeaturesWithCommaSeparatedIds() throws Exception { 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(nakshaHttpUri + "hub/spaces/" + spaceId + "/features?" + idsQueryParam)) - .GET() - .header(HDR_STREAM_ID, streamId) - .build(); - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + response = nakshaClient.get("hub/spaces/" + spaceId + "/features?" + idsQueryParam, streamId); // Then: Perform assertions standardAssertions(response, 200, expectedBodyPart, streamId);