Compare commits

..

No commits in common. "87e8576ce7144b54c52f6db4a54168981972c5fc" and "fde50a045410e812d75d62cf0f71cfe070ccc77b" have entirely different histories.

4 changed files with 44 additions and 94 deletions

View File

@ -5,7 +5,6 @@ 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.CreateSelfDebitorForPartner;
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateSepaMandataForDebitor; 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.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.debitor.InvalidateSepaMandateForDebitor;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateMembership; import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateMembership;
import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContactToPartner; import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContactToPartner;
@ -17,27 +16,18 @@ 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.SubscribeToMailinglist;
import net.hostsharing.hsadminng.hs.office.scenarios.subscription.UnsubscribeFromMailinglist; import net.hostsharing.hsadminng.hs.office.scenarios.subscription.UnsubscribeFromMailinglist;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
@Tag("scenarioTest")
@SpringBootTest( @SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { HsadminNgApplication.class, JpaAttempt.class }, classes = { HsadminNgApplication.class, JpaAttempt.class }
properties = {
"spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///scenariosTC}",
"spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:ADMIN}",
"spring.datasource.password=${HSADMINNG_POSTGRES_ADMIN_PASSWORD:password}",
"hsadminng.superuser=${HSADMINNG_SUPERUSER:superuser-alex@hostsharing.net}"
}
) )
@DirtiesContext @Tag("useCaseTest")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class HsOfficeScenarioTests extends ScenarioTest { class HsOfficeScenarioTests extends ScenarioTest {
@ -156,17 +146,6 @@ class HsOfficeScenarioTests extends ScenarioTest {
.doRun(); .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 @Test
@Order(3100) @Order(3100)
@Requires("Debitor: Test AG - main debitor") @Requires("Debitor: Test AG - main debitor")
@ -218,7 +197,6 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Produces("Subscription: Michael Miller to operations-announce") @Produces("Subscription: Michael Miller to operations-announce")
void shouldSubscribeNewPersonAndContactToMailinglist() { void shouldSubscribeNewPersonAndContactToMailinglist() {
new SubscribeToMailinglist(this) 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("partnerPersonUuid", "%{Person: Test AG}")
.given("subscriberFamilyName", "Miller") .given("subscriberFamilyName", "Miller")
.given("subscriberGivenName", "Michael") .given("subscriberGivenName", "Michael")

View File

@ -35,6 +35,10 @@ import static org.assertj.core.api.Assertions.assertThat;
public abstract class ScenarioTest extends ContextBasedTest { public abstract class ScenarioTest extends ContextBasedTest {
final static String RUN_AS_USER = "superuser-alex@hostsharing.net"; // TODO.test: use global:AGENT when implemented 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 @Getter
private PrintWriter markdownFile; private PrintWriter markdownFile;
@ -103,7 +107,7 @@ public abstract class ScenarioTest extends ContextBasedTest {
private void createTestLogMarkdownFile(final TestInfo testInfo) throws IOException { private void createTestLogMarkdownFile(final TestInfo testInfo) throws IOException {
final var testMethodName = testInfo.getTestMethod().map(Method::getName).orElseThrow(); final var testMethodName = testInfo.getTestMethod().map(Method::getName).orElseThrow();
final var testMethodOrder = testInfo.getTestMethod().map(m -> m.getAnnotation(Order.class).value()).orElseThrow(); final var testMethodOrder = testInfo.getTestMethod().map(m -> m.getAnnotation(Order.class).value()).orElseThrow();
markdownFile = new PrintWriter(new FileWriter("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md")); markdownFile = new PrintWriter(new FileWriter(testMethodOrder + "-" + testMethodName + ".md"));
print("## Scenario: " + testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2")); print("## Scenario: " + testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2"));
} }

View File

@ -1,7 +1,6 @@
package net.hostsharing.hsadminng.hs.office.scenarios; package net.hostsharing.hsadminng.hs.office.scenarios;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import lombok.SneakyThrows;
import net.hostsharing.hsadminng.reflection.AnnotationFinder; import net.hostsharing.hsadminng.reflection.AnnotationFinder;
import org.apache.commons.collections4.map.LinkedMap; import org.apache.commons.collections4.map.LinkedMap;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
@ -9,13 +8,10 @@ import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; 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.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -25,10 +21,11 @@ import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.assertThat; 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.isBlank;
import static org.junit.platform.commons.util.StringUtils.isNotBlank; import static org.junit.platform.commons.util.StringUtils.isNotBlank;
import static org.springframework.http.MediaType.APPLICATION_JSON;
public abstract class UseCase<T extends UseCase<?>> { public abstract class UseCase<T extends UseCase<?>> {
private static final HttpClient client = HttpClient.newHttpClient(); private static final RestClient restClient = RestClient.create();
private final ScenarioTest testSuite; private final ScenarioTest testSuite;
private final Map<String, Function<String, UseCase<?>>> requirements = new LinkedMap<>(); private final Map<String, Function<String, UseCase<?>>> requirements = new LinkedMap<>();
@ -82,45 +79,34 @@ public abstract class UseCase<T extends UseCase<?>> {
this.nextTitle = null; this.nextTitle = null;
} }
@SneakyThrows
public final HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) { public final HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) {
final var requestBody = bodyJsonTemplate.resolvePlaceholders(); final var body = bodyJsonTemplate.resolvePlaceholders();
final var request = HttpRequest.newBuilder() final var response = restClient.post()
.method(HttpMethod.POST.toString(), BodyPublishers.ofString(requestBody)) .uri("http://localhost:" + testSuite.port + uriPath)
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
.header("Content-Type", "application/json")
.header("current-subject", ScenarioTest.RUN_AS_USER) .header("current-subject", ScenarioTest.RUN_AS_USER)
.timeout(Duration.ofSeconds(10)) .contentType(APPLICATION_JSON)
.build(); .body(body)
final var response = client.send(request, BodyHandlers.ofString()); .retrieve();
return new HttpResponse(HttpMethod.POST, uriPath, requestBody, response); return new HttpResponse(HttpMethod.POST, uriPath, body, response);
} }
@SneakyThrows
public final HttpResponse httpPatch(final String uriPath, final JsonTemplate bodyJsonTemplate) { public final HttpResponse httpPatch(final String uriPath, final JsonTemplate bodyJsonTemplate) {
final var requestBody = bodyJsonTemplate.resolvePlaceholders(); final var body = bodyJsonTemplate.resolvePlaceholders();
final var request = HttpRequest.newBuilder() final var response = restClient.patch()
.method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody)) .uri("http://localhost:" + testSuite.port + uriPath)
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
.header("Content-Type", "application/json")
.header("current-subject", ScenarioTest.RUN_AS_USER) .header("current-subject", ScenarioTest.RUN_AS_USER)
.timeout(Duration.ofSeconds(10)) .contentType(APPLICATION_JSON)
.build(); .body(body)
final var response = client.send(request, BodyHandlers.ofString()); .retrieve();
return new HttpResponse(HttpMethod.PATCH, uriPath, requestBody, response); return new HttpResponse(HttpMethod.PATCH, uriPath, body, response);
} }
@SneakyThrows
public final HttpResponse httpDelete(final String uriPath) { public final HttpResponse httpDelete(final String uriPath) {
final var request = HttpRequest.newBuilder() final var response = restClient.delete()
.DELETE() .uri("http://localhost:" + testSuite.port + uriPath)
.uri(new URI("http://localhost:" + testSuite.port + uriPath)) .header("current-subject", ScenarioTest.RUN_AS_USER)
.header("Content-Type", "application/json") .retrieve();
.header("current-subject", ScenarioTest.RUN_AS_USER) return new HttpResponse(HttpMethod.DELETE, uriPath, null, response);
.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) { public final UUID uuid(final String alias) {
@ -142,20 +128,20 @@ public abstract class UseCase<T extends UseCase<?>> {
public class HttpResponse { public class HttpResponse {
private final java.net.http.HttpResponse<String> response; private final ResponseEntity<String> response;
private final HttpStatus status; private final HttpStatusCode status;
private UUID locationUuid; private UUID locationUuid;
public HttpResponse( public HttpResponse(
final HttpMethod httpMethod, final HttpMethod httpMethod,
final String uri, final String uri,
final String requestBody, final String requestBody,
final java.net.http.HttpResponse<String> response final RestClient.ResponseSpec responseSpec
) { ) {
this.response = response; response = responseSpec.toEntity(String.class);
this.status = HttpStatus.valueOf(response.statusCode()); status = this.response.getStatusCode();
if (this.status == HttpStatus.CREATED) { if (this.status.value() == HttpStatus.CREATED.value()) {
final var location = response.headers().firstValue("Location").orElseThrow(); final var location = response.getHeaders().getLocation().toString();
assertThat(location).startsWith("http://localhost:"); assertThat(location).startsWith("http://localhost:");
locationUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1)); locationUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1));
} }
@ -170,20 +156,22 @@ public abstract class UseCase<T extends UseCase<?>> {
printLine(requestBody + "=> status: " + status + " " + printLine(requestBody + "=> status: " + status + " " +
(locationUuid != null ? locationUuid : "")); (locationUuid != null ? locationUuid : ""));
if (!status.is2xxSuccessful()) { if (!status.is2xxSuccessful()) {
printLine(response.body()); // FIXME: prettyPrint printLine(responseSpec.body(String.class)); // FIXME: prettyPrint
} }
printLine("```"); printLine("```");
printLine(""); printLine("");
} }
public HttpResponse expecting(final HttpStatus httpStatus) { public HttpResponse expecting(final HttpStatus httpStatus) {
assertThat(HttpStatus.valueOf(response.statusCode())).isEqualTo(httpStatus); assertThat(HttpStatus.valueOf(response.getStatusCode().value())).isEqualTo(httpStatus);
return this; return this;
} }
public HttpResponse expecting(final ContentType contentType) { public HttpResponse expecting(final ContentType contentType) {
assertThat(response.headers().firstValue("content-type")) assertThat(response.getHeaders().getContentType())
.contains(contentType.toString()); .isNotNull()
.extracting(Object::toString)
.isEqualTo(contentType.toString());
return this; return this;
} }

View File

@ -1,20 +0,0 @@
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<DontDeleteDefaultDebitor> {
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;
}
}