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
7 changed files with 113 additions and 70 deletions
Showing only changes of commit 4d29c4643b - Show all commits

View File

@ -25,16 +25,14 @@ class HsOfficeUseCasesTest extends UseCaseTest {
@Test @Test
@Order(1010) @Order(1010)
void shouldCreatePartner() { void shouldCreatePartner() {
keep("partner:Test AG.uuid", () -> new CreatePartner(this, "Partner: Test AG")
new CreatePartner(this)
.given("partnerNumber", 30001) .given("partnerNumber", 30001)
.given("personType", "LEGAL_PERSON") .given("personType", "LEGAL_PERSON")
.given("tradeName", "Test AG") .given("tradeName", "Test AG")
.given("contactCaption", "Test AG - Bord of Directors") .given("contactCaption", "Test AG - Bord of Directors")
.given("emailAddress", "bord-of-directors@test-ag.example.org") .given("emailAddress", "bord-of-directors@test-ag.example.org")
.doRun() .doRun()
.keepingAs() .keep();
);
} }
@Test @Test
@ -48,7 +46,9 @@ class HsOfficeUseCasesTest extends UseCaseTest {
@Test @Test
@Order(2000) @Order(2000)
void shouldCreateSelfDebitorForPartner() { void shouldCreateSelfDebitorForPartner() {
new CreateSelfDebitorForPartner(this).doRun(); new CreateSelfDebitorForPartner(this, "Debitor: Test AG - main debitor")
.doRun()
.keep();
} }
@Test @Test

View File

@ -16,8 +16,11 @@ 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.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.CREATE;
@ -29,19 +32,26 @@ import static org.hamcrest.Matchers.startsWith;
public abstract class UseCase<T extends UseCase<?>> { public abstract class UseCase<T extends UseCase<?>> {
private final UseCaseTest testSuite; private final UseCaseTest testSuite;
private final Map<String, Supplier<UseCase<?>>> requirements = new LinkedMap<>(); private final Map<String, Function<String, UseCase<?>>> requirements = new LinkedMap<>();
private final String resultAlias;
private String nextTitle; private String nextTitle;
public UseCase(final UseCaseTest testSuite) { public UseCase(final UseCaseTest testSuite) {
this.testSuite = testSuite; this(testSuite, null);
log();
log("## Testcase " + this.getClass().getSimpleName().replaceAll("([a-z])([A-Z]+)", "$1 $2"));
log("");
} }
public final void requires(final String alias, final Supplier<UseCase<?>> useCaseSupplier) { public UseCase(final UseCaseTest testSuite, final String resultAlias) {
this.testSuite = testSuite;
this.resultAlias = resultAlias;
if (resultAlias != null) {
log("### UseCase " + resultAlias);
log("");
}
}
public final void requires(final String alias, final Function<String, UseCase<?>> useCaseFactory) {
if ( !UseCaseTest.aliases.containsKey(alias) ) { if ( !UseCaseTest.aliases.containsKey(alias) ) {
requirements.put(alias, useCaseSupplier); requirements.put(alias, useCaseFactory);
} }
} }
@ -53,7 +63,7 @@ public abstract class UseCase<T extends UseCase<?>> {
} }
public final HttpResponse doRun() { public final HttpResponse doRun() {
requirements.forEach((alias, supplier) -> supplier.get().run().keepingAs(alias)); requirements.forEach((alias, factpory) -> factpory.apply(alias).run());
return run(); return run();
} }
@ -70,7 +80,7 @@ public abstract class UseCase<T extends UseCase<?>> {
public final void keep(final String alias, final Supplier<HttpResponse> http) { public final void keep(final String alias, final Supplier<HttpResponse> http) {
this.nextTitle = alias; // FIXME: ugly this.nextTitle = alias; // FIXME: ugly
http.get().keepingAs(alias); http.get().keep();
this.nextTitle = null; this.nextTitle = null;
} }
@ -108,10 +118,7 @@ public abstract class UseCase<T extends UseCase<?>> {
String resolvePlaceholders() { String resolvePlaceholders() {
var partiallyResolved = new AtomicReference<>(template); var partiallyResolved = new AtomicReference<>(template);
Streams.concat( UseCaseTest.knowVariables().forEach(entry -> partiallyResolved.set(
UseCaseTest.aliases.entrySet().stream().map(e -> Map.entry(e.getKey(), e.getValue().uuid())),
UseCaseTest.properties.entrySet().stream()
).forEach(entry -> partiallyResolved.set(
partiallyResolved.get().replace("${" + entry.getKey() + "}", optionallyQuoted(entry.getValue()))) partiallyResolved.get().replace("${" + entry.getKey() + "}", optionallyQuoted(entry.getValue())))
); );
verifyAllPlaceholdersResolved(partiallyResolved.get()); verifyAllPlaceholdersResolved(partiallyResolved.get());
@ -135,7 +142,12 @@ public abstract class UseCase<T extends UseCase<?>> {
unresolvedPlaceholders.add(matcher.group()); unresolvedPlaceholders.add(matcher.group());
} }
assertThat(unresolvedPlaceholders).as("unresolved placeholders").hasSize(0); final var knownVariables = UseCaseTest.knowVariables()
.map(e -> '"' + e.getKey() + "\": \"" + e.getValue() + '"')
.collect(Collectors.joining("\n "));
assertThat(unresolvedPlaceholders)
.as("known variables: [\n " + knownVariables + "\n]\nunresolved placeholders:")
.isEmpty();
} }
} }
@ -162,6 +174,8 @@ public abstract class UseCase<T extends UseCase<?>> {
if (nextTitle != null) { if (nextTitle != null) {
log("\n### " + nextTitle + "\n"); log("\n### " + nextTitle + "\n");
} else if (resultAlias != null) {
log("\n### " + resultAlias + "\n");
} }
log("```"); log("```");
log(httpMethod.name() + " " + uri); log(httpMethod.name() + " " + uri);
@ -181,19 +195,15 @@ public abstract class UseCase<T extends UseCase<?>> {
return this; return this;
} }
public void keepingAs(final String uuidAliasName) { public void keep() {
UseCaseTest.aliases.put(uuidAliasName, new UseCaseTest.Alias(self().getClass(), locationUuid)); UseCaseTest.aliases.put(
nextTitle != null ? nextTitle : resultAlias,
new UseCaseTest.Alias<>(UseCase.this.getClass(), locationUuid));
} }
} }
@SneakyThrows
void log() {
Files.write(Paths.get(getClass().getSimpleName() + ".md"), "".getBytes(), CREATE, TRUNCATE_EXISTING);
}
@SneakyThrows
void log(final String output) { void log(final String output) {
Files.write(Paths.get(getClass().getSimpleName() + ".md"), (output+"\n").getBytes(), APPEND); testSuite.log(output);
} }
protected T self() { protected T self() {

View File

@ -1,5 +1,8 @@
package net.hostsharing.hsadminng.hs.office.usecases; package net.hostsharing.hsadminng.hs.office.usecases;
import com.tngtech.archunit.thirdparty.com.google.common.collect.Streams;
import lombok.Getter;
import lombok.SneakyThrows;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.lambda.Reducer; import net.hostsharing.hsadminng.lambda.Reducer;
@ -7,19 +10,28 @@ 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.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
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.io.FileWriter;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream;
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
@Getter
public static TestInfo currentTestInfo;
@Getter
private PrintWriter markdownFile;
record Alias<T extends UseCase<T>>(Class<T> useCase, UUID uuid) {} record Alias<T extends UseCase<T>>(Class<T> useCase, UUID uuid) {}
final static Map<String, Alias<?>> aliases = new HashMap<>(); final static Map<String, Alias<?>> aliases = new HashMap<>();
@ -34,13 +46,14 @@ public abstract class UseCaseTest extends ContextBasedTest {
@Autowired @Autowired
JpaAttempt jpaAttempt; JpaAttempt jpaAttempt;
@SneakyThrows
@BeforeEach @BeforeEach
void init() { void init(final TestInfo testInfo) {
jpaAttempt.transacted(() -> jpaAttempt.transacted(() ->
{ {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
aliases.put( aliases.put(
"person:Hostsharing eG.uuid", "Person: Hostsharing eG",
new Alias<>( new Alias<>(
null, null,
personRepo.findPersonByOptionalNameLike("Hostsharing eG") personRepo.findPersonByOptionalNameLike("Hostsharing eG")
@ -50,10 +63,28 @@ public abstract class UseCaseTest extends ContextBasedTest {
); );
} }
); );
final var testMethodName = testInfo.getTestMethod().map(Method::getName).orElseThrow();
markdownFile = new PrintWriter(new FileWriter(testMethodName + ".md"));
log("## Testcase " + testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2"));
currentTestInfo = testInfo; // FIXME: remove?
} }
@AfterEach @AfterEach
void cleanup() { void cleanup() {
properties.clear(); properties.clear();
markdownFile.close();
}
@SneakyThrows
void log(final String output) {
markdownFile.println(output);
}
static Stream<Map.Entry<String,?>> knowVariables() {
return Streams.concat(
UseCaseTest.aliases.entrySet().stream().map(e -> Map.entry(e.getKey(), e.getValue().uuid())),
UseCaseTest.properties.entrySet().stream()
);
} }
} }

View File

@ -8,15 +8,15 @@ import static org.springframework.http.HttpStatus.CREATED;
public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPartner> { public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPartner> {
public CreateSelfDebitorForPartner(final UseCaseTest testSuite) { public CreateSelfDebitorForPartner(final UseCaseTest testSuite, final String resultAlias) {
super(testSuite); super(testSuite, resultAlias);
requires("person:Test AG.uuid"); requires("Person: Test AG");
} }
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
keep("bankaccount:Test AG - refund bank account.uuid", () -> keep("BankAccount: Test AG - refund bank account", () ->
httpPost("/api/hs/office/bankaccounts", usingJsonBody(""" httpPost("/api/hs/office/bankaccounts", usingJsonBody("""
{ {
"holder": "Test AG - refund bank account", "holder": "Test AG - refund bank account",
@ -27,7 +27,7 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
.expecting(CREATED).expecting(JSON) .expecting(CREATED).expecting(JSON)
); );
keep("contact:Test AG - billing department.uuid", () -> keep("Contact: Test AG - billing department", () ->
httpPost("/api/hs/office/contacts", usingJsonBody(""" httpPost("/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": "Test AG - billing department", "caption": "Test AG - billing department",
@ -39,14 +39,13 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
.expecting(CREATED).expecting(JSON) .expecting(CREATED).expecting(JSON)
); );
keep("debitor:Test AG - Hauptdebitor.uuid", () -> return httpPost("/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
"anchorUuid": ${person:Test AG.uuid}, "anchorUuid": ${person:Test AG.uuid},
"holderUuid": ${person:Test AG.uuid}, "holderUuid": ${person:Test AG.uuid},
"contactUuid": ${contact:Test AG - billing department.uuid} "contactUuid": ${Contact: Test AG - billing department}
}, },
"debitorNumberSuffix": "00", "debitorNumberSuffix": "00",
"billable": true, "billable": true,
@ -54,12 +53,10 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
"vatCountryCode": "DE", "vatCountryCode": "DE",
"vatBusiness": true, "vatBusiness": true,
"vatReverseCharge": false, "vatReverseCharge": false,
"refundBankAccountUuid": ${bankaccount:Test AG - refund bank account.uuid}, "refundBankAccountUuid": ${BankAccount: Test AG - refund bank account},
"defaultPrefix": "tst" "defaultPrefix": "tst"
} }
""")) """))
.expecting(CREATED).expecting(JSON) .expecting(CREATED).expecting(JSON);
);
return null;
} }
} }

View File

@ -15,6 +15,7 @@ public class CreateMembership extends UseCase<CreateMembership> {
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
keep("membership:Test AG 00.uuid", () ->
httpPost("/api/hs/office/memberships", usingJsonBody(""" httpPost("/api/hs/office/memberships", usingJsonBody("""
{ {
"partnerUuid": "${partner:Test AG.uuid}", "partnerUuid": "${partner:Test AG.uuid}",
@ -24,7 +25,7 @@ public class CreateMembership extends UseCase<CreateMembership> {
} }
""")) """))
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON) .expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
.keepingAs("membership:Test AG 00.uuid"); );
return null; return null;
} }
} }

View File

@ -8,13 +8,17 @@ import org.springframework.http.HttpStatus;
public class CreatePartner extends UseCase<CreatePartner> { public class CreatePartner extends UseCase<CreatePartner> {
public CreatePartner(final UseCaseTest testSuite) { public CreatePartner(final UseCaseTest testSuite) {
super(testSuite); super(testSuite, null);
}
public CreatePartner(final UseCaseTest testSuite, final String resultAlias) {
super(testSuite, resultAlias);
} }
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
keep("person:Test AG.uuid", () -> keep("Person: Test AG", () ->
httpPost("/api/hs/office/persons", usingJsonBody(""" httpPost("/api/hs/office/persons", usingJsonBody("""
{ {
"personType": ${personType}, "personType": ${personType},
@ -24,7 +28,7 @@ public class CreatePartner extends UseCase<CreatePartner> {
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON) .expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
); );
keep("contact:Test AG - Bord of Directors.uuid", () -> keep("Contact: Test AG - Bord of Directors", () ->
httpPost("/api/hs/office/contacts", usingJsonBody(""" httpPost("/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": ${contactCaption}, "caption": ${contactCaption},
@ -40,9 +44,9 @@ public class CreatePartner extends UseCase<CreatePartner> {
{ {
"partnerNumber": ${partnerNumber}, "partnerNumber": ${partnerNumber},
"partnerRel": { "partnerRel": {
"anchorUuid": ${person:Hostsharing eG.uuid}, "anchorUuid": ${Person: Hostsharing eG},
"holderUuid": ${person:Test AG.uuid}, "holderUuid": ${Person: Test AG},
"contactUuid": ${contact:Test AG - Bord of Directors.uuid} "contactUuid": ${Contact: Test AG - Bord of Directors}
}, },
"details": { "details": {
"registrationOffice": "Registergericht Hamburg", "registrationOffice": "Registergericht Hamburg",

View File

@ -9,7 +9,7 @@ public class DeletePartner extends UseCase<DeletePartner> {
public DeletePartner(final UseCaseTest testSuite) { public DeletePartner(final UseCaseTest testSuite) {
super(testSuite); super(testSuite);
requires("partner:Delete AG:uuid", () -> new CreatePartner(testSuite) requires("Partner: Delete AG", alias -> new CreatePartner(testSuite, alias)
.given("personType", "LEGAL_PERSON") .given("personType", "LEGAL_PERSON")
.given("tradeName", "Delete AG") .given("tradeName", "Delete AG")
.given("contactCaption", "Delete AG - Bord of Directors") .given("contactCaption", "Delete AG - Bord of Directors")
@ -18,7 +18,7 @@ public class DeletePartner extends UseCase<DeletePartner> {
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
httpDelete("/api/hs/office/partners/" + uuid("partner:Delete AG:uuid")) httpDelete("/api/hs/office/partners/" + uuid("Partner: Delete AG"))
.expecting(HttpStatus.NO_CONTENT); .expecting(HttpStatus.NO_CONTENT);
return null; return null;
} }