From f899f447b2192087dc50247b28993934d3a027cb Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 25 Oct 2024 11:03:50 +0200 Subject: [PATCH] use java.net.http.HttpClient --- .../scenarios/HsOfficeScenarioTests.java | 14 +++ .../hs/office/scenarios/ScenarioTest.java | 6 +- .../hs/office/scenarios/UseCase.java | 86 +++++++++++-------- .../debitor/DontDeleteDefaultDebitor.java | 20 +++++ 4 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/DontDeleteDefaultDebitor.java diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index 583726fc..06f8b410 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateExternalDebit import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateSelfDebitorForPartner; import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateSepaMandataForDebitor; import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DeleteSepaMandataForDebitor; +import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DontDeleteDefaultDebitor; import net.hostsharing.hsadminng.hs.office.scenarios.debitor.InvalidateSepaMandateForDebitor; import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateMembership; import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContactToPartner; @@ -16,6 +17,7 @@ import net.hostsharing.hsadminng.hs.office.scenarios.subscription.RemoveOperatio import net.hostsharing.hsadminng.hs.office.scenarios.subscription.SubscribeToMailinglist; import net.hostsharing.hsadminng.hs.office.scenarios.subscription.UnsubscribeFromMailinglist; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; @@ -146,6 +148,17 @@ class HsOfficeScenarioTests extends ScenarioTest { .doRun(); } + @Test + @Order(2020) + @Requires("Debitor: Test AG - main debitor") + @Disabled("FIXME: Should not delete, but does delete, but we need it for subsequent tests. See TODO.spec inside.") + void shouldNotDeleteDefaultDebitor() { + new DontDeleteDefaultDebitor(this) + .given("partnerNumber", 31020) + .given("debitorSuffix", "00") + .doRun(); + } + @Test @Order(3100) @Requires("Debitor: Test AG - main debitor") @@ -197,6 +210,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Produces("Subscription: Michael Miller to operations-announce") void shouldSubscribeNewPersonAndContactToMailinglist() { new SubscribeToMailinglist(this) + // TODO.spec: do we need the personType? or is an operational contact always a natural person? what about distribution lists? .given("partnerPersonUuid", "%{Person: Test AG}") .given("subscriberFamilyName", "Miller") .given("subscriberGivenName", "Michael") diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/ScenarioTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/ScenarioTest.java index d4cd93f0..7b14e2a4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/ScenarioTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/ScenarioTest.java @@ -35,10 +35,6 @@ import static org.assertj.core.api.Assertions.assertThat; public abstract class ScenarioTest extends ContextBasedTest { final static String RUN_AS_USER = "superuser-alex@hostsharing.net"; // TODO.test: use global:AGENT when implemented - //String producesAlias; - - // @Getter - // public static TestInfo currentTestInfo; @Getter private PrintWriter markdownFile; @@ -107,7 +103,7 @@ public abstract class ScenarioTest extends ContextBasedTest { private void createTestLogMarkdownFile(final TestInfo testInfo) throws IOException { final var testMethodName = testInfo.getTestMethod().map(Method::getName).orElseThrow(); final var testMethodOrder = testInfo.getTestMethod().map(m -> m.getAnnotation(Order.class).value()).orElseThrow(); - markdownFile = new PrintWriter(new FileWriter(testMethodOrder + "-" + testMethodName + ".md")); + markdownFile = new PrintWriter(new FileWriter("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md")); print("## Scenario: " + testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2")); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UseCase.java index 810102be..9c93a111 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UseCase.java @@ -1,6 +1,7 @@ package net.hostsharing.hsadminng.hs.office.scenarios; import io.restassured.http.ContentType; +import lombok.SneakyThrows; import net.hostsharing.hsadminng.reflection.AnnotationFinder; import org.apache.commons.collections4.map.LinkedMap; import org.hibernate.AssertionFailure; @@ -8,10 +9,13 @@ import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.RestClient; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; @@ -21,11 +25,10 @@ import java.util.function.Supplier; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.util.StringUtils.isBlank; import static org.junit.platform.commons.util.StringUtils.isNotBlank; -import static org.springframework.http.MediaType.APPLICATION_JSON; public abstract class UseCase> { - private static final RestClient restClient = RestClient.create(); + private static final HttpClient client = HttpClient.newHttpClient(); private final ScenarioTest testSuite; private final Map>> requirements = new LinkedMap<>(); @@ -79,34 +82,45 @@ public abstract class UseCase> { this.nextTitle = null; } + @SneakyThrows public final HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) { - final var body = bodyJsonTemplate.resolvePlaceholders(); - final var response = restClient.post() - .uri("http://localhost:" + testSuite.port + uriPath) + final var requestBody = bodyJsonTemplate.resolvePlaceholders(); + final var request = HttpRequest.newBuilder() + .method(HttpMethod.POST.toString(), BodyPublishers.ofString(requestBody)) + .uri(new URI("http://localhost:" + testSuite.port + uriPath)) + .header("Content-Type", "application/json") .header("current-subject", ScenarioTest.RUN_AS_USER) - .contentType(APPLICATION_JSON) - .body(body) - .retrieve(); - return new HttpResponse(HttpMethod.POST, uriPath, body, response); + .timeout(Duration.ofSeconds(10)) + .build(); + final var response = client.send(request, BodyHandlers.ofString()); + return new HttpResponse(HttpMethod.POST, uriPath, requestBody, response); } + @SneakyThrows public final HttpResponse httpPatch(final String uriPath, final JsonTemplate bodyJsonTemplate) { - final var body = bodyJsonTemplate.resolvePlaceholders(); - final var response = restClient.patch() - .uri("http://localhost:" + testSuite.port + uriPath) + final var requestBody = bodyJsonTemplate.resolvePlaceholders(); + final var request = HttpRequest.newBuilder() + .method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody)) + .uri(new URI("http://localhost:" + testSuite.port + uriPath)) + .header("Content-Type", "application/json") .header("current-subject", ScenarioTest.RUN_AS_USER) - .contentType(APPLICATION_JSON) - .body(body) - .retrieve(); - return new HttpResponse(HttpMethod.PATCH, uriPath, body, response); + .timeout(Duration.ofSeconds(10)) + .build(); + final var response = client.send(request, BodyHandlers.ofString()); + return new HttpResponse(HttpMethod.PATCH, uriPath, requestBody, response); } + @SneakyThrows public final HttpResponse httpDelete(final String uriPath) { - final var response = restClient.delete() - .uri("http://localhost:" + testSuite.port + uriPath) - .header("current-subject", ScenarioTest.RUN_AS_USER) - .retrieve(); - return new HttpResponse(HttpMethod.DELETE, uriPath, null, response); + final var request = HttpRequest.newBuilder() + .DELETE() + .uri(new URI("http://localhost:" + testSuite.port + uriPath)) + .header("Content-Type", "application/json") + .header("current-subject", ScenarioTest.RUN_AS_USER) + .timeout(Duration.ofSeconds(10)) + .build(); + final var response = client.send(request, BodyHandlers.ofString()); + return new HttpResponse(HttpMethod.PATCH, uriPath, null, response); } public final UUID uuid(final String alias) { @@ -128,20 +142,20 @@ public abstract class UseCase> { public class HttpResponse { - private final ResponseEntity response; - private final HttpStatusCode status; + private final java.net.http.HttpResponse response; + private final HttpStatus status; private UUID locationUuid; public HttpResponse( final HttpMethod httpMethod, final String uri, final String requestBody, - final RestClient.ResponseSpec responseSpec + final java.net.http.HttpResponse response ) { - response = responseSpec.toEntity(String.class); - status = this.response.getStatusCode(); - if (this.status.value() == HttpStatus.CREATED.value()) { - final var location = response.getHeaders().getLocation().toString(); + this.response = response; + this.status = HttpStatus.valueOf(response.statusCode()); + if (this.status == HttpStatus.CREATED) { + final var location = response.headers().firstValue("Location").orElseThrow(); assertThat(location).startsWith("http://localhost:"); locationUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1)); } @@ -156,22 +170,20 @@ public abstract class UseCase> { printLine(requestBody + "=> status: " + status + " " + (locationUuid != null ? locationUuid : "")); if (!status.is2xxSuccessful()) { - printLine(responseSpec.body(String.class)); // FIXME: prettyPrint + printLine(response.body()); // FIXME: prettyPrint } printLine("```"); printLine(""); } public HttpResponse expecting(final HttpStatus httpStatus) { - assertThat(HttpStatus.valueOf(response.getStatusCode().value())).isEqualTo(httpStatus); + assertThat(HttpStatus.valueOf(response.statusCode())).isEqualTo(httpStatus); return this; } public HttpResponse expecting(final ContentType contentType) { - assertThat(response.getHeaders().getContentType()) - .isNotNull() - .extracting(Object::toString) - .isEqualTo(contentType.toString()); + assertThat(response.headers().firstValue("content-type")) + .contains(contentType.toString()); return this; } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/DontDeleteDefaultDebitor.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/DontDeleteDefaultDebitor.java new file mode 100644 index 00000000..82aae503 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/DontDeleteDefaultDebitor.java @@ -0,0 +1,20 @@ +package net.hostsharing.hsadminng.hs.office.scenarios.debitor; + +import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest; +import net.hostsharing.hsadminng.hs.office.scenarios.UseCase; +import org.springframework.http.HttpStatus; + +public class DontDeleteDefaultDebitor extends UseCase { + + public DontDeleteDefaultDebitor(final ScenarioTest testSuite) { + super(testSuite); + } + + @Override + protected HttpResponse run() { + httpDelete("/api/hs/office/debitors/" + uuid("Debitor: Test AG - main debitor")) + // TODO.spec: should be CONFLICT or CLIENT_ERROR for Debitor "00" - but how to delete Partners? + .expecting(HttpStatus.NO_CONTENT); + return null; + } +}