feature/use-case-acceptance-tests #116

Merged
hsh-michaelhoennig merged 49 commits from feature/use-case-acceptance-tests into master 2024-10-30 11:40:46 +01:00
8 changed files with 126 additions and 56 deletions
Showing only changes of commit 2b21c742f0 - Show all commits

View File

@ -112,7 +112,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
} }
}, },
"debitorNumber": 1000111, "debitorNumber": 1000111,
"debitorNumberSuffix": 11, "debitorNumberSuffix": "11",
"partner": { "partner": {
"partnerNumber": 10001, "partnerNumber": 10001,
"partnerRel": { "partnerRel": {
@ -167,7 +167,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
} }
}, },
"debitorNumber": 1000212, "debitorNumber": 1000212,
"debitorNumberSuffix": 12, "debitorNumberSuffix": "12",
"partner": { "partner": {
"partnerNumber": 10002, "partnerNumber": 10002,
"partnerRel": { "partnerRel": {
@ -201,7 +201,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
} }
}, },
"debitorNumber": 1000313, "debitorNumber": 1000313,
"debitorNumberSuffix": 13, "debitorNumberSuffix": "13",
"partner": { "partner": {
"partnerNumber": 10003, "partnerNumber": 10003,
"partnerRel": { "partnerRel": {
@ -469,7 +469,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
} }
}, },
"debitorNumber": 1000111, "debitorNumber": 1000111,
"debitorNumberSuffix": 11, "debitorNumberSuffix": "11",
"partner": { "partner": {
"partnerNumber": 10001, "partnerNumber": 10001,
"partnerRel": { "partnerRel": {
@ -581,7 +581,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"contact": { "caption": "fourth contact" } "contact": { "caption": "fourth contact" }
}, },
"debitorNumber": 10004${debitorNumberSuffix}, "debitorNumber": 10004${debitorNumberSuffix},
"debitorNumberSuffix": ${debitorNumberSuffix}, "debitorNumberSuffix": "${debitorNumberSuffix}",
"partner": { "partner": {
"partnerNumber": 10004, "partnerNumber": 10004,
"partnerRel": { "partnerRel": {

View File

@ -1,27 +1,27 @@
package net.hostsharing.hsadminng.hs.office.usecases; package net.hostsharing.hsadminng.hs.office.usecases;
import io.restassured.http.ContentType;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.Method.POST; class HsOfficeCreatePartnerUseCase extends UseCase {
class HsOfficePartnerUseCase extends UseCase { public HsOfficeCreatePartnerUseCase(final UseCaseTest testSuite) {
public HsOfficePartnerUseCase(final UseCaseTest testSuite) {
super(testSuite); super(testSuite);
} }
void shouldCreatePartner() { @Override
HttpResponse run() {
http(POST, "/api/hs/office/persons", usingJsonBody(""" httpPost("/api/hs/office/persons", usingJsonBody("""
{ {
"personType": "LEGAL_PERSON", "personType": "LEGAL_PERSON",
"tradeName": "Test AG" "tradeName": "Test AG"
} }
""")) """))
.expecting(HttpStatus.CREATED) .expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
.keepingAs("person:Test AG.uuid"); .keepingAs("person:Test AG.uuid");
http(POST, "/api/hs/office/contacts", usingJsonBody(""" httpPost("/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": "Test AG - Bord of Directors", "caption": "Test AG - Bord of Directors",
"emailAddresses": { "emailAddresses": {
@ -29,10 +29,10 @@ class HsOfficePartnerUseCase extends UseCase {
} }
} }
""")) """))
.expecting(HttpStatus.CREATED) .expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
.keepingAs("contact:Test AG - Bord of Directors.uuid"); .keepingAs("contact:Test AG - Bord of Directors.uuid");
http(POST, "/api/hs/office/partners", usingJsonBody(""" return httpPost("/api/hs/office/partners", usingJsonBody("""
{ {
"partnerNumber": "30003", "partnerNumber": "30003",
"partnerRel": { "partnerRel": {
@ -46,7 +46,6 @@ class HsOfficePartnerUseCase extends UseCase {
} }
} }
""")) """))
.expecting(HttpStatus.CREATED) .expecting(HttpStatus.CREATED).expecting(ContentType.JSON);
.keepingAs("partner:Test AG.uuid");
} }
} }

View File

@ -1,28 +1,29 @@
package net.hostsharing.hsadminng.hs.office.usecases; package net.hostsharing.hsadminng.hs.office.usecases;
import org.springframework.http.HttpStatus; import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.CREATED;
import static io.restassured.http.Method.POST;
class HsOfficeDebitorUseCase extends UseCase { class HsOfficeDebitorUseCase extends UseCase {
public HsOfficeDebitorUseCase(final UseCaseTest testSuite) { public HsOfficeDebitorUseCase(final UseCaseTest testSuite) {
super(testSuite); super(testSuite);
requires("person:Test AG.uuid", () -> new HsOfficeCreatePartnerUseCase(testSuite));
} }
void shouldCreateSelfDebitorForPartner() { @Override
HttpResponse run() {
http(POST, "/api/hs/office/bankaccounts", usingJsonBody(""" httpPost("/api/hs/office/bankaccounts", usingJsonBody("""
{ {
"holder": "Test AG - refund bank account", "holder": "Test AG - refund bank account",
"iban": "DE88100900001234567892", "iban": "DE88100900001234567892",
"bic": "BEVODEBB" "bic": "BEVODEBB"
} }
""")) """))
.expecting(HttpStatus.CREATED) .expecting(CREATED).expecting(JSON)
.keepingAs("bankaccount:Test AG - refund bank account.uuid"); .keepingAs("bankaccount:Test AG - refund bank account.uuid");
http(POST, "/api/hs/office/contacts", usingJsonBody(""" httpPost("/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": "Test AG - billing department", "caption": "Test AG - billing department",
"emailAddresses": { "emailAddresses": {
@ -30,10 +31,10 @@ class HsOfficeDebitorUseCase extends UseCase {
} }
} }
""")) """))
.expecting(HttpStatus.CREATED) .expecting(CREATED).expecting(JSON)
.keepingAs("contact:Test AG - billing department.uuid"); .keepingAs("contact:Test AG - billing department.uuid");
http(POST, "/api/hs/office/debitors", usingJsonBody(""" httpPost("/api/hs/office/debitors", usingJsonBody("""
{ {
"debitorRel": { "debitorRel": {
"type": "DEBITOR", // FIXME: should be defaulted to DEBITOR "type": "DEBITOR", // FIXME: should be defaulted to DEBITOR
@ -51,7 +52,8 @@ class HsOfficeDebitorUseCase extends UseCase {
"defaultPrefix": "tst" "defaultPrefix": "tst"
} }
""")) """))
.expecting(HttpStatus.CREATED) .expecting(CREATED).expecting(JSON)
.keepingAs("debitor:Test AG - Hauptdebitor.uuid"); .keepingAs("debitor:Test AG - Hauptdebitor.uuid");
return null;
} }
} }

View File

@ -0,0 +1,19 @@
package net.hostsharing.hsadminng.hs.office.usecases;
import org.springframework.http.HttpStatus;
class HsOfficeDeletePartnerUseCase extends UseCase {
public HsOfficeDeletePartnerUseCase(final UseCaseTest testSuite) {
super(testSuite);
requires("partner:Test AG for deletetion:uuid", () -> new HsOfficeCreatePartnerUseCase(testSuite));
}
@Override
HttpResponse run() {
httpDelete("/api/hs/office/partners/" + uuid("partner:Test AG.uuid"))
.expecting(HttpStatus.NO_CONTENT);
return null;
}
}

View File

@ -1,17 +1,17 @@
package net.hostsharing.hsadminng.hs.office.usecases; package net.hostsharing.hsadminng.hs.office.usecases;
import io.restassured.http.ContentType;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.Method.POST;
class HsOfficeMembershipUseCase extends UseCase { class HsOfficeMembershipUseCase extends UseCase {
public HsOfficeMembershipUseCase(final UseCaseTest testSuite) { public HsOfficeMembershipUseCase(final UseCaseTest testSuite) {
super(testSuite); super(testSuite);
} }
void shouldCreateMembershipForPartner() { @Override
http(POST, "/api/hs/office/memberships", usingJsonBody(""" HttpResponse run() {
httpPost("/api/hs/office/memberships", usingJsonBody("""
{ {
"partnerUuid": "${partner:Test AG.uuid}", "partnerUuid": "${partner:Test AG.uuid}",
"memberNumberSuffix": "00", "memberNumberSuffix": "00",
@ -19,7 +19,8 @@ class HsOfficeMembershipUseCase extends UseCase {
"membershipFeeBillable": "true" "membershipFeeBillable": "true"
} }
""")) """))
.expecting(HttpStatus.CREATED) .expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
.keepingAs("membership:Test AG 00.uuid"); .keepingAs("membership:Test AG 00.uuid");
return null;
} }
} }

View File

@ -2,11 +2,12 @@ package net.hostsharing.hsadminng.hs.office.usecases;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.ClassOrderer; 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.TestClassOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest( @SpringBootTest(
@ -14,24 +15,32 @@ import org.springframework.boot.test.context.SpringBootTest;
classes = { HsadminNgApplication.class, JpaAttempt.class } classes = { HsadminNgApplication.class, JpaAttempt.class }
) )
@Tag("useCaseTest") @Tag("useCaseTest")
@TestClassOrder(ClassOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ExtendWith(OrderedDependedTestsExtension.class)
class HsOfficeUseCasesTest extends UseCaseTest { class HsOfficeUseCasesTest extends UseCaseTest {
@Test @Test
@Order(1010) @Order(1010)
void shouldCreatePartner() { void shouldCreatePartner() {
new HsOfficePartnerUseCase(this).shouldCreatePartner(); new HsOfficeCreatePartnerUseCase(this).run()
.keepingAs("partner:Test AG.uuid");
}
@Test
@Order(1011)
void shouldDeletePartner() {
new HsOfficeDeletePartnerUseCase(this).run();
} }
@Test @Test
@Order(1020) @Order(1020)
void shouldCreateSelfDebitorForPartner() { void shouldCreateSelfDebitorForPartner() {
new HsOfficeDebitorUseCase(this).shouldCreateSelfDebitorForPartner(); new HsOfficeDebitorUseCase(this).run();
} }
@Test @Test
@Order(1030) @Order(1030)
void shouldCreateMembershipForPartner() { void shouldCreateMembershipForPartner() {
new HsOfficeMembershipUseCase(this).shouldCreateMembershipForPartner(); new HsOfficeMembershipUseCase(this).run();
} }
} }

View File

@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.hs.office.usecases;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.http.Method;
import io.restassured.response.Response; import io.restassured.response.Response;
import io.restassured.response.ValidatableResponse; import io.restassured.response.ValidatableResponse;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -11,12 +10,13 @@ import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
public class UseCase { public abstract class UseCase {
private final UseCaseTest testSuite; private final UseCaseTest testSuite;
@ -24,24 +24,40 @@ public class UseCase {
this.testSuite = testSuite; this.testSuite = testSuite;
} }
void requires(final String alias, final Supplier<UseCase> useCaseSupplier) {
if ( !UseCaseTest.aliases.containsKey(alias) ) {
useCaseSupplier.get().run().keepingAs(alias);
}
}
abstract HttpResponse run();
JsonTemplate usingJsonBody(final String jsonTemplate) { JsonTemplate usingJsonBody(final String jsonTemplate) {
return new JsonTemplate(jsonTemplate); return new JsonTemplate(jsonTemplate);
} }
HttpResponse http(final Method method, final String uriPath, final JsonTemplate bodyJsonTemplate) { HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) {
final var request = RestAssured.given() final var response = RestAssured.given()
.header("current-subject", UseCaseTest.RUN_AS_USER) .header("current-subject", UseCaseTest.RUN_AS_USER)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(bodyJsonTemplate.with(UseCaseTest.aliases)) .body(bodyJsonTemplate.with(UseCaseTest.aliases))
.port(testSuite.port); .port(testSuite.port)
final var response = .when().post("http://localhost" + uriPath);
switch (method) {
case POST -> request.when().post("http://localhost" + uriPath);
default -> throw new IllegalStateException("HTTP method not implemented yet: " + method);
};
return new HttpResponse(response); return new HttpResponse(response);
} }
HttpResponse httpDelete(final String uriPath) {
final var response = RestAssured.given()
.header("current-subject", UseCaseTest.RUN_AS_USER)
.port(testSuite.port)
.when().delete("http://localhost" + uriPath);
return new HttpResponse(response);
}
UUID uuid(final String alias) {
return testSuite.aliases.get(alias);
}
static class JsonTemplate { static class JsonTemplate {
private final String template; private final String template;
@ -50,10 +66,10 @@ public class UseCase {
this.template = jsonTemplate; this.template = jsonTemplate;
} }
String with(final Map<String, String> aliases) { String with(final Map<String, UUID> aliases) {
var partiallyResolved = new AtomicReference<>(template); var partiallyResolved = new AtomicReference<>(template);
aliases.forEach((k, v) -> aliases.forEach((k, v) ->
partiallyResolved.set(partiallyResolved.get().replace("${" + k + "}", v))); partiallyResolved.set(partiallyResolved.get().replace("${" + k + "}", v.toString())));
verifyAllPlaceholdersResolved(partiallyResolved.get()); verifyAllPlaceholdersResolved(partiallyResolved.get());
return partiallyResolved.get(); return partiallyResolved.get();
} }
@ -80,8 +96,12 @@ public class UseCase {
} }
HttpResponse expecting(final HttpStatus httpStatus) { HttpResponse expecting(final HttpStatus httpStatus) {
response.statusCode(httpStatus.value()) response.statusCode(httpStatus.value());
.contentType(ContentType.JSON); return this;
}
HttpResponse expecting(final ContentType contentType) {
response.contentType(contentType);
return this; return this;
} }
@ -91,7 +111,7 @@ public class UseCase {
final var newSubjectUuid = UUID.fromString( final var newSubjectUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1)); location.substring(location.lastIndexOf('/') + 1));
assertThat(newSubjectUuid).isNotNull(); assertThat(newSubjectUuid).isNotNull();
UseCaseTest.aliases.put(uuidAliasName, newSubjectUuid.toString()); UseCaseTest.aliases.put(uuidAliasName, newSubjectUuid);
} }
} }
} }

View File

@ -6,17 +6,23 @@ import net.hostsharing.hsadminng.lambda.Reducer;
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest; import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import static org.assertj.core.api.Assumptions.assumeThat;
public abstract class UseCaseTest extends ContextBasedTest { public abstract class UseCaseTest 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
final static Map<String, String> aliases = new HashMap<>(); final static Map<String, UUID> aliases = new HashMap<>();
@LocalServerPort @LocalServerPort
Integer port; Integer port;
@ -37,9 +43,23 @@ public abstract class UseCaseTest extends ContextBasedTest {
personRepo.findPersonByOptionalNameLike("Hostsharing eG") personRepo.findPersonByOptionalNameLike("Hostsharing eG")
.stream() .stream()
.map(HsOfficePersonEntity::getUuid) .map(HsOfficePersonEntity::getUuid)
.map(Object::toString)
.reduce(Reducer::toSingleElement).orElseThrow()); .reduce(Reducer::toSingleElement).orElseThrow());
} }
); );
} }
} }
class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback {
private static boolean previousTestsPassed = true;
@Override
public void testFailed(final ExtensionContext context, final Throwable cause) {
previousTestsPassed = false;
}
@Override
public void beforeEach(final ExtensionContext extensionContext) {
assumeThat(previousTestsPassed).isTrue();
}
}