diff --git a/src/main/java/net/hostsharing/hsadminng/errors/CustomErrorResponse.java b/src/main/java/net/hostsharing/hsadminng/errors/CustomErrorResponse.java index 3df51ebb..2455ee76 100644 --- a/src/main/java/net/hostsharing/hsadminng/errors/CustomErrorResponse.java +++ b/src/main/java/net/hostsharing/hsadminng/errors/CustomErrorResponse.java @@ -46,6 +46,7 @@ public class CustomErrorResponse { this.path = path; this.statusCode = status.value(); this.statusPhrase = status.getReasonPhrase(); + // HOWTO: debug serverside error response - set a breakpoint here this.message = message.startsWith("ERROR: [") ? message : "ERROR: [" + statusCode + "] " + message; } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java index 7a491961..12c373da 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java @@ -16,6 +16,8 @@ import java.util.List; import java.util.UUID; import java.util.function.BiConsumer; +import static java.util.Optional.ofNullable; + @RestController public class HsOfficeMembershipController implements HsOfficeMembershipsApi { @@ -39,7 +41,8 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi { context.define(currentSubject, assumedRoles); final var entities = ( memberNumber != null) - ? List.of(membershipRepo.findMembershipByMemberNumber(memberNumber)) + // FIXME: RestTest for the case that findMembershipByMemberNumber returns null + ? ofNullable(membershipRepo.findMembershipByMemberNumber(memberNumber)).stream().toList() : membershipRepo.findMembershipsByOptionalPartnerUuid(partnerUuid); final var resources = mapper.mapList(entities, HsOfficeMembershipResource.class, 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 b08e9f3c..2ed73c37 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 @@ -12,6 +12,7 @@ import net.hostsharing.hsadminng.hs.office.scenarios.debitor.FinallyDeleteSepaMa 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.CancelMembership; +import net.hostsharing.hsadminng.hs.office.scenarios.membership.CoopSharesTransactionUseCase; 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.CreatePartner; @@ -49,7 +50,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(1010) - @Produces(explicitly = "Partner: Test AG", implicitly = {"Person: Test AG", "Contact: Test AG - Hamburg"}) + @Produces(explicitly = "Partner: P-31010 - Test AG", implicitly = {"Person: Test AG", "Contact: Test AG - Hamburg"}) void shouldCreateLegalPersonAsPartner() { new CreatePartner(this) .given("partnerNumber", 31010) @@ -71,7 +72,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(1011) - @Produces(explicitly = "Partner: Michelle Matthieu", implicitly = {"Person: Michelle Matthieu", "Contact: Michelle Matthieu"}) + @Produces(explicitly = "Partner: P-31011 - Michelle Matthieu", implicitly = {"Person: Michelle Matthieu", "Contact: Michelle Matthieu"}) void shouldCreateNaturalPersonAsPartner() { new CreatePartner(this) .given("partnerNumber", 31011) @@ -148,7 +149,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(1100) - @Requires("Partner: Michelle Matthieu") + @Requires("Partner: P-31011 - Michelle Matthieu") void shouldAmendContactData() { new AmendContactData(this) .given("partnerName", "Matthieu") @@ -158,7 +159,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(1101) - @Requires("Partner: Michelle Matthieu") + @Requires("Partner: P-31011 - Michelle Matthieu") void shouldAddPhoneNumberToContactData() { new AddPhoneNumberToContactData(this) .given("partnerName", "Matthieu") @@ -169,7 +170,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(1102) - @Requires("Partner: Michelle Matthieu") + @Requires("Partner: P-31011 - Michelle Matthieu") void shouldRemovePhoneNumberFromContactData() { new RemovePhoneNumberFromContactData(this) .given("partnerName", "Matthieu") @@ -179,7 +180,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(1103) - @Requires("Partner: Test AG") + @Requires("Partner: P-31010 - Test AG") void shouldReplaceContactData() { new ReplaceContactData(this) .given("partnerName", "Test AG") @@ -201,7 +202,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(1201) - @Requires("Partner: Michelle Matthieu") + @Requires("Partner: P-31011 - Michelle Matthieu") void shouldUpdatePersonData() { new ShouldUpdatePersonData(this) .given("oldFamilyName", "Matthieu") @@ -211,7 +212,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(2010) - @Requires("Partner: Test AG") + @Requires("Partner: P-31010 - Test AG") @Produces("Debitor: Test AG - main debitor") void shouldCreateSelfDebitorForPartner() { new CreateSelfDebitorForPartner(this, "Debitor: Test AG - main debitor") @@ -313,7 +314,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(4000) - @Requires("Partner: Test AG") + @Requires("Partner: P-31010 - Test AG") @Produces("Membership: Test AG 00") void shouldCreateMembershipForPartner() { new CreateMembership(this) @@ -326,6 +327,51 @@ class HsOfficeScenarioTests extends ScenarioTest { .keep(); } + @Test + @Order(4200) + @Requires("Membership: Test AG 00") + @Produces("Coop-Shares SUBSCRIPTION Transaction") + void testCoopSharesSubscriptionTransaction() { + new CoopSharesTransactionUseCase(this) + .given("memberNumber", "3101000") + .given("transactionType", "SUBSCRIPTION") + .given("reference", "sign 2024-01-15") + .given("shareCount", "100") + .given("comment", "Signing the Membership") + .given("transactionDate", "2024-01-15") + .doRun(); + } + + @Test + @Order(4201) + @Requires("Coop-Shares SUBSCRIPTION Transaction") + @Produces("Coop-Shares ADJUSTMENT Transaction") + void testCoopSharesAdjustmentTransaction() { + new CoopSharesTransactionUseCase(this) + .given("memberNumber", "3102000") + .given("transactionType", "ADJUSTMENT") + .given("reference", "adjust 2024-01-16") + .given("shareCount", "-90") + .given("comment", "Cancelling 90 Shares, correcting wrong number of digits in subscription") + .given("transactionDate", "2024-01-16") + .doRun(); + } + + @Test + @Order(4202) + @Requires("Coop-Shares SUBSCRIPTION Transaction") + @Produces("Coop-Shares CANCELLATION Transaction") + void testCoopSharesCancellationTransaction() { + new CoopSharesTransactionUseCase(this) + .given("memberNumber", "3102000") + .given("transactionType", "CANCELLATION") + .given("reference", "cancel 2024-01-15") + .given("shareCount", "8") + .given("comment", "Cancelling 8 Shares") + .given("transactionDate", "2024-02-15") + .doRun(); + } + @Test @Order(4900) @Requires("Membership: Test AG 00") diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/PathAssertion.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/PathAssertion.java index ffd9df8e..baf8d358 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/PathAssertion.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/PathAssertion.java @@ -4,6 +4,8 @@ import net.hostsharing.hsadminng.hs.office.scenarios.UseCase.HttpResponse; import java.util.function.Consumer; +import static org.junit.jupiter.api.Assertions.fail; + public class PathAssertion { private final String path; @@ -14,10 +16,25 @@ public class PathAssertion { @SuppressWarnings({ "unchecked", "rawtypes" }) public Consumer contains(final String resolvableValue) { - return response -> response.path(path).contains(ScenarioTest.resolve(resolvableValue)); + return response -> { + try { + // FIXME: typed check instead of .map(Object::toString) possible? + response.path(path).map(Object::toString).contains(ScenarioTest.resolve(resolvableValue)); + } catch (final AssertionError e) { + // without this, the error message is often lacking important context + fail(e.getMessage() + " in `path(\"" + path + "\").contains(\"" + resolvableValue + "\")`" ); + } + }; } public Consumer doesNotExist() { - return response -> response.path(path).isNull(); // here, null Optional means key not found in JSON + return response -> { + try { + response.path(path).isNull(); // here, null Optional means key not found in JSON + } catch (final AssertionError e) { + // without this, the error message is often lacking important context + fail(e.getMessage() + " in `path(\"" + path + "\").doesNotExist()`" ); + } + }; } } 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 f224fff2..ccd4200f 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 @@ -302,16 +302,17 @@ public abstract class UseCase> { } @SneakyThrows - public Optional getFromBodyAsOptional(final String path) { + public Optional getFromBodyAsOptional(final String path) { try { return Optional.ofNullable(JsonPath.parse(response.body()).read(ScenarioTest.resolve(path))); } catch (final Exception e) { + // FIXME: catch more precise exception class return null; // means the property did not exist at all, not that it was there with value null } } @SneakyThrows - public OptionalAssert path(final String path) { + public OptionalAssert path(final String path) { return assertThat(getFromBodyAsOptional(path)); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CoopSharesTransactionUseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CoopSharesTransactionUseCase.java new file mode 100644 index 00000000..589469da --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CoopSharesTransactionUseCase.java @@ -0,0 +1,51 @@ +package net.hostsharing.hsadminng.hs.office.scenarios.membership; + +import io.restassured.http.ContentType; +import net.hostsharing.hsadminng.hs.office.scenarios.UseCase; +import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest; +import org.springframework.http.HttpStatus; + +import static io.restassured.http.ContentType.JSON; +import static org.springframework.http.HttpStatus.OK; + +public class CoopSharesTransactionUseCase extends UseCase { + + public CoopSharesTransactionUseCase(final ScenarioTest testSuite) { + super(testSuite); + } + + @Override + protected HttpResponse run() { + + obtain("membershipUuid", () -> + httpGet("/api/hs/office/memberships?memberNumber=&{memberNumber}") + .expecting(OK).expecting(JSON).expectArrayElements(1), + response -> response.getFromBody("$[0].uuid") + ); + + return httpPost("/api/hs/office/coopsharestransactions", usingJsonBody(""" + { + "membership.uuid": ${membershipUuid}, + "transactionType": ${transactionType}, + "reference": ${reference}, + "shareCount": ${shareCount}, + "comment": ${comment}, + "valueDate": ${transactionDate} + } + """)) + .expecting(HttpStatus.CREATED).expecting(ContentType.JSON); + } + + @Override + protected void verify(final HttpResponse response) { + verify("Verify Coop-Shares %{transactionType}-Transaction", + () -> httpGet("/api/hs/office/coopsharestransactions/" + response.getLocationUuid()) + .expecting(HttpStatus.OK).expecting(ContentType.JSON), +// path("transactionType").contains("%{transactionType}"), +// path("memberNumber").contains("%{memberNumber}"), + path("shareCount").contains("%{shareCount}"), + path("comment").contains("%{comment}"), + path("valueDate").contains("%{transactionDate}") + ); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateMembership.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateMembership.java index 379bba1e..f66a0202 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateMembership.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateMembership.java @@ -16,9 +16,11 @@ public class CreateMembership extends UseCase { @Override protected HttpResponse run() { + // FIXME: httpGet "partner.uuid": ${Partner: Test AG} + return httpPost("/api/hs/office/memberships", usingJsonBody(""" { - "partner.uuid": ${Partner: Test AG}, + "partner.uuid": ${Partner: P-31010 - Test AG}, "memberNumberSuffix": ${memberNumberSuffix}, "status": "ACTIVE", "validFrom": ${validFrom},