scenario test for multiple debitors+memberships + validation for subsequent memberships (#160)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/160 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
		| @@ -32,6 +32,41 @@ create table if not exists hs_office.membership | ||||
| --// | ||||
|  | ||||
|  | ||||
| -- ============================================================================ | ||||
| --changeset michael.hoennig:hs-office-membership-SINGLE-MEMBERSHIP-CHECK endDelimiter:--// | ||||
| -- ---------------------------------------------------------------------------- | ||||
|  | ||||
| CREATE OR REPLACE FUNCTION hs_office.validate_membership_validity() | ||||
|     RETURNS trigger AS $$ | ||||
| DECLARE | ||||
|     partnerNumber int; | ||||
| BEGIN | ||||
|     IF EXISTS ( | ||||
|         SELECT 1 | ||||
|             FROM hs_office.membership | ||||
|             WHERE partnerUuid = NEW.partnerUuid | ||||
|               AND uuid <> NEW.uuid | ||||
|               AND NEW.validity && validity | ||||
|     ) THEN | ||||
|         SELECT p.partnerNumber INTO partnerNumber | ||||
|             FROM hs_office.partner AS p | ||||
|             WHERE p.uuid = NEW.partnerUuid; | ||||
|         RAISE EXCEPTION 'Membership validity ranges overlap for partnerUuid %, partnerNumber %', NEW.partnerUuid, partnerNumber; | ||||
|     END IF; | ||||
|  | ||||
|     RETURN NEW; | ||||
| END; | ||||
| $$ LANGUAGE plpgsql; | ||||
|  | ||||
| CREATE TRIGGER trg_validate_membership_validity | ||||
|     BEFORE INSERT OR UPDATE ON hs_office.membership | ||||
|     FOR EACH ROW | ||||
| EXECUTE FUNCTION hs_office.validate_membership_validity(); | ||||
|  | ||||
|  | ||||
| --// | ||||
|  | ||||
|  | ||||
| -- ============================================================================ | ||||
| --changeset michael.hoennig:hs-office-membership-MAIN-TABLE-JOURNAL endDelimiter:--// | ||||
| -- ---------------------------------------------------------------------------- | ||||
|   | ||||
| @@ -10,7 +10,8 @@ | ||||
|  */ | ||||
| create or replace procedure hs_office.membership_create_test_data( | ||||
|         forPartnerNumber numeric(5), | ||||
|         newMemberNumberSuffix char(2) ) | ||||
|         newMemberNumberSuffix char(2), | ||||
|         validity daterange) | ||||
|     language plpgsql as $$ | ||||
| declare | ||||
|     relatedPartner          hs_office.partner; | ||||
| @@ -22,7 +23,7 @@ begin | ||||
|     raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner; | ||||
|     insert | ||||
|         into hs_office.membership (uuid, partneruuid, memberNumberSuffix, validity, status) | ||||
|         values (uuid_generate_v4(), relatedPartner.uuid, newMemberNumberSuffix, daterange('20221001' , null, '[]'), 'ACTIVE'); | ||||
|         values (uuid_generate_v4(), relatedPartner.uuid, newMemberNumberSuffix, validity, 'ACTIVE'); | ||||
| end; $$; | ||||
| --// | ||||
|  | ||||
| @@ -35,9 +36,9 @@ do language plpgsql $$ | ||||
|     begin | ||||
|         call base.defineContext('creating Membership test-data', null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN'); | ||||
|  | ||||
|         call hs_office.membership_create_test_data(10001, '01'); | ||||
|         call hs_office.membership_create_test_data(10002, '02'); | ||||
|         call hs_office.membership_create_test_data(10003, '03'); | ||||
|         call hs_office.membership_create_test_data(10001, '01', daterange('20221001' , '20241231', '[)')); | ||||
|         call hs_office.membership_create_test_data(10002, '02', daterange('20221001' , '20251231', '[]')); | ||||
|         call hs_office.membership_create_test_data(10003, '03', daterange('20221001' , null, '[]')); | ||||
|     end; | ||||
| $$; | ||||
| --// | ||||
|   | ||||
| @@ -86,7 +86,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                               "memberNumber": "M-1000101", | ||||
|                               "memberNumberSuffix": "01", | ||||
|                               "validFrom": "2022-10-01", | ||||
|                               "validTo": null, | ||||
|                               "validTo": "2024-12-30", | ||||
|                               "status": "ACTIVE" | ||||
|                           }, | ||||
|                           { | ||||
| @@ -94,7 +94,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                               "memberNumber": "M-1000202", | ||||
|                               "memberNumberSuffix": "02", | ||||
|                               "validFrom": "2022-10-01", | ||||
|                               "validTo": null, | ||||
|                               "validTo": "2025-12-31", | ||||
|                               "status": "ACTIVE" | ||||
|                           }, | ||||
|                           { | ||||
| @@ -133,7 +133,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                               "memberNumber": "M-1000101", | ||||
|                               "memberNumberSuffix": "01", | ||||
|                               "validFrom": "2022-10-01", | ||||
|                               "validTo": null, | ||||
|                               "validTo": "2024-12-30", | ||||
|                               "status": "ACTIVE" | ||||
|                           } | ||||
|                       ] | ||||
| @@ -161,7 +161,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                                   "memberNumber": "M-1000202", | ||||
|                                   "memberNumberSuffix": "02", | ||||
|                                   "validFrom": "2022-10-01", | ||||
|                                   "validTo": null, | ||||
|                                   "validTo": "2025-12-31", | ||||
|                                   "status": "ACTIVE" | ||||
|                               } | ||||
|                           ] | ||||
| @@ -177,7 +177,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|         void globalAdmin_canAddMembership() { | ||||
|  | ||||
|             context.define("superuser-alex@hostsharing.net"); | ||||
|             final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Third").get(0); | ||||
|             final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").getFirst(); | ||||
|             final var givenMemberSuffix = TEMP_MEMBER_NUMBER_SUFFIX; | ||||
|             final var expectedMemberNumber = Integer.parseInt(givenPartner.getPartnerNumber() + TEMP_MEMBER_NUMBER_SUFFIX); | ||||
|  | ||||
| @@ -189,7 +189,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                                { | ||||
|                                    "partner.uuid": "%s", | ||||
|                                    "memberNumberSuffix": "%s", | ||||
|                                    "validFrom": "2022-10-13", | ||||
|                                    "validFrom": "2025-02-13", | ||||
|                                    "membershipFeeBillable": "true" | ||||
|                                  } | ||||
|                             """.formatted(givenPartner.getUuid(), givenMemberSuffix)) | ||||
| @@ -200,10 +200,10 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                         .statusCode(201) | ||||
|                         .contentType(ContentType.JSON) | ||||
|                         .body("uuid", isUuidValid()) | ||||
|                         .body("partner.partnerNumber", is("P-10003")) | ||||
|                         .body("partner.partnerNumber", is("P-10001")) | ||||
|                         .body("memberNumber", is("M-" + expectedMemberNumber)) | ||||
|                         .body("memberNumberSuffix", is(givenMemberSuffix)) | ||||
|                         .body("validFrom", is("2022-10-13")) | ||||
|                         .body("validFrom", is("2025-02-13")) | ||||
|                         .body("validTo", equalTo(null)) | ||||
|                         .header("Location", startsWith("http://localhost")) | ||||
|                     .extract().header("Location");  // @formatter:on | ||||
| @@ -239,7 +239,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                          "memberNumber": "M-1000101", | ||||
|                          "memberNumberSuffix": "01", | ||||
|                          "validFrom": "2022-10-01", | ||||
|                          "validTo": null, | ||||
|                          "validTo": "2024-12-30", | ||||
|                          "status": "ACTIVE" | ||||
|                      } | ||||
|                     """)); // @formatter:on | ||||
| @@ -297,13 +297,13 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|             context.define("superuser-alex@hostsharing.net"); | ||||
|             final var givenMembership = givenSomeTemporaryMembershipBessler("First"); | ||||
|  | ||||
|             final var location = RestAssured // @formatter:off | ||||
|             RestAssured // @formatter:off | ||||
|                 .given() | ||||
|                     .header("current-subject", "superuser-alex@hostsharing.net") | ||||
|                     .contentType(ContentType.JSON) | ||||
|                     .body(""" | ||||
|                            { | ||||
|                                "validTo": "2023-12-31", | ||||
|                                "validTo": "2025-12-31", | ||||
|                                "status": "CANCELLED" | ||||
|                            } | ||||
|                           """) | ||||
| @@ -316,8 +316,8 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                     .body("uuid", isUuidValid()) | ||||
|                     .body("partner.partnerNumber", is("P-" + givenMembership.getPartner().getPartnerNumber())) | ||||
|                     .body("memberNumberSuffix", is(givenMembership.getMemberNumberSuffix())) | ||||
|                     .body("validFrom", is("2022-11-01")) | ||||
|                     .body("validTo", is("2023-12-31")) | ||||
|                     .body("validFrom", is("2025-02-01")) | ||||
|                     .body("validTo", is("2025-12-31")) | ||||
|                     .body("status", is("CANCELLED")); | ||||
|             // @formatter:on | ||||
|  | ||||
| @@ -326,7 +326,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                     .matches(mandate -> { | ||||
|                         assertThat(mandate.getPartner().toShortString()).isEqualTo("P-10001"); | ||||
|                         assertThat(mandate.getMemberNumberSuffix()).isEqualTo(givenMembership.getMemberNumberSuffix()); | ||||
|                         assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-01)"); | ||||
|                         assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2026-01-01)"); | ||||
|                         assertThat(mandate.getStatus()).isEqualTo(CANCELLED); | ||||
|                         return true; | ||||
|                     }); | ||||
| @@ -348,7 +348,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|                     .contentType(ContentType.JSON) | ||||
|                     .body(""" | ||||
|                            { | ||||
|                                "validTo": "2024-01-01", | ||||
|                                "validTo": "2025-12-31", | ||||
|                                "status": "CANCELLED" | ||||
|                            } | ||||
|                            """) | ||||
| @@ -361,7 +361,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|             // finally, the Membership is actually updated | ||||
|             assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() | ||||
|                     .matches(mandate -> { | ||||
|                         assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-02)"); | ||||
|                         assertThat(mandate.getValidity().asString()).isEqualTo("[2025-02-01,2026-01-01)"); | ||||
|                         assertThat(mandate.getStatus()).isEqualTo(CANCELLED); | ||||
|                         return true; | ||||
|                     }); | ||||
| @@ -434,7 +434,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle | ||||
|             final var newMembership = HsOfficeMembershipEntity.builder() | ||||
|                     .partner(givenPartner) | ||||
|                     .memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX) | ||||
|                     .validity(Range.closedInfinite(LocalDate.parse("2022-11-01"))) | ||||
|                     .validity(Range.closedInfinite(LocalDate.parse("2025-02-01"))) | ||||
|                     .status(ACTIVE) | ||||
|                     .membershipFeeBillable(true) | ||||
|                     .build(); | ||||
|   | ||||
| @@ -4,19 +4,20 @@ import io.hypersistence.utils.hibernate.type.range.Range; | ||||
| import net.hostsharing.hsadminng.context.Context; | ||||
| import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; | ||||
| import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository; | ||||
| import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; | ||||
| import net.hostsharing.hsadminng.mapper.Array; | ||||
| import net.hostsharing.hsadminng.rbac.grant.RawRbacGrantRepository; | ||||
| import net.hostsharing.hsadminng.rbac.role.RawRbacRoleRepository; | ||||
| import net.hostsharing.hsadminng.mapper.Array; | ||||
| import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; | ||||
| import net.hostsharing.hsadminng.rbac.test.JpaAttempt; | ||||
| import org.junit.jupiter.api.Nested; | ||||
| import org.junit.jupiter.api.Tag; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.postgresql.util.PSQLException; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; | ||||
| import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||||
| import org.springframework.context.annotation.Import; | ||||
| import org.springframework.orm.jpa.JpaSystemException; | ||||
| import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||||
|  | ||||
| import jakarta.persistence.EntityManager; | ||||
| import jakarta.persistence.PersistenceContext; | ||||
| @@ -31,7 +32,7 @@ import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
|  | ||||
| @DataJpaTest | ||||
| @Import( { Context.class, JpaAttempt.class }) | ||||
| @Import({ Context.class, JpaAttempt.class }) | ||||
| @Tag("officeIntegrationTest") | ||||
| class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCleanup { | ||||
|  | ||||
| @@ -70,15 +71,16 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|             final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0); | ||||
|  | ||||
|             // when | ||||
|             final var result = attempt(em, () -> { | ||||
|                 final var newMembership = HsOfficeMembershipEntity.builder() | ||||
|                         .memberNumberSuffix("11") | ||||
|                         .partner(givenPartner) | ||||
|                         .validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) | ||||
|                         .membershipFeeBillable(true) | ||||
|                         .build(); | ||||
|                 return toCleanup(membershipRepo.save(newMembership).load()); | ||||
|             }); | ||||
|             final var result = attempt( | ||||
|                     em, () -> { | ||||
|                         final var newMembership = HsOfficeMembershipEntity.builder() | ||||
|                                 .memberNumberSuffix("11") | ||||
|                                 .partner(givenPartner) | ||||
|                                 .validity(Range.closedInfinite(LocalDate.parse("2025-01-01"))) | ||||
|                                 .membershipFeeBillable(true) | ||||
|                                 .build(); | ||||
|                         return toCleanup(membershipRepo.save(newMembership).load()); | ||||
|                     }); | ||||
|  | ||||
|             // then | ||||
|             result.assertSuccessful(); | ||||
| @@ -87,6 +89,31 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|             assertThat(membershipRepo.count()).isEqualTo(count + 1); | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
|         public void creatingMembershipForSamePartnerIsDisallowedIfAnotherOneIsStillActive() { | ||||
|             // given | ||||
|             context("superuser-alex@hostsharing.net"); | ||||
|             final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").getFirst(); | ||||
|  | ||||
|             // when | ||||
|             final var result = attempt( | ||||
|                     em, () -> { | ||||
|                         final var newMembership = HsOfficeMembershipEntity.builder() | ||||
|                                 .memberNumberSuffix("11") | ||||
|                                 .partner(givenPartner) | ||||
|                                 .validity(Range.closedInfinite(LocalDate.parse("2024-01-01"))) | ||||
|                                 .membershipFeeBillable(true) | ||||
|                                 .build(); | ||||
|                         return toCleanup(membershipRepo.save(newMembership).load()); | ||||
|                     }); | ||||
|  | ||||
|             // then | ||||
|             result.assertExceptionWithRootCauseMessage( | ||||
|                     PSQLException.class, | ||||
|                     "Membership validity ranges overlap for partnerUuid " + givenPartner.getUuid() + | ||||
|                             ", partnerNumber " + givenPartner.getPartnerNumber()); | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
|         public void createsAndGrantsRoles() { | ||||
|             // given | ||||
| @@ -97,16 +124,17 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|                     .toList(); | ||||
|  | ||||
|             // when | ||||
|             attempt(em, () -> { | ||||
|                 final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0); | ||||
|                 final var newMembership = HsOfficeMembershipEntity.builder() | ||||
|                         .memberNumberSuffix("17") | ||||
|                         .partner(givenPartner) | ||||
|                         .validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) | ||||
|                         .membershipFeeBillable(true) | ||||
|                         .build(); | ||||
|                 return toCleanup(membershipRepo.save(newMembership)); | ||||
|             }).assertSuccessful(); | ||||
|             attempt( | ||||
|                     em, () -> { | ||||
|                         final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0); | ||||
|                         final var newMembership = HsOfficeMembershipEntity.builder() | ||||
|                                 .memberNumberSuffix("17") | ||||
|                                 .partner(givenPartner) | ||||
|                                 .validity(Range.closedInfinite(LocalDate.parse("2025-01-01"))) | ||||
|                                 .membershipFeeBillable(true) | ||||
|                                 .build(); | ||||
|                         return toCleanup(membershipRepo.save(newMembership)); | ||||
|                     }).assertSuccessful(); | ||||
|  | ||||
|             // then | ||||
|             final var all = rawRoleRepo.findAll(); | ||||
| @@ -145,7 +173,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|  | ||||
|         private void assertThatMembershipIsPersisted(final HsOfficeMembershipEntity saved) { | ||||
|             final var found = membershipRepo.findByUuid(saved.getUuid()); | ||||
|             assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString()) ; | ||||
|             assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -163,8 +191,8 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|             // then | ||||
|             exactlyTheseMembershipsAreReturned( | ||||
|                     result, | ||||
|                     "Membership(M-1000101, P-10001, [2022-10-01,), ACTIVE)", | ||||
|                     "Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)", | ||||
|                     "Membership(M-1000101, P-10001, [2022-10-01,2024-12-31), ACTIVE)", | ||||
|                     "Membership(M-1000202, P-10002, [2022-10-01,2026-01-01), ACTIVE)", | ||||
|                     "Membership(M-1000303, P-10003, [2022-10-01,), ACTIVE)"); | ||||
|         } | ||||
|  | ||||
| @@ -178,8 +206,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|             final var result = membershipRepo.findMembershipsByPartnerUuid(givenPartner.getUuid()); | ||||
|  | ||||
|             // then | ||||
|             exactlyTheseMembershipsAreReturned(result, | ||||
|                     "Membership(M-1000101, P-10001, [2022-10-01,), ACTIVE)"); | ||||
|             exactlyTheseMembershipsAreReturned( | ||||
|                     result, | ||||
|                     "Membership(M-1000101, P-10001, [2022-10-01,2024-12-31), ACTIVE)"); | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
| @@ -194,7 +223,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|             assertThat(result) | ||||
|                     .isNotNull() | ||||
|                     .extracting(Object::toString) | ||||
|                     .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); | ||||
|                     .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,2026-01-01), ACTIVE)"); | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
| @@ -209,7 +238,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|             assertThat(result) | ||||
|                     .isNotNull() | ||||
|                     .extracting(Object::toString) | ||||
|                     .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); | ||||
|                     .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,2026-01-01), ACTIVE)"); | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
| @@ -221,8 +250,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|             final var result = membershipRepo.findMembershipsByPartnerNumber(10002); | ||||
|  | ||||
|             // then | ||||
|             exactlyTheseMembershipsAreReturned(result, | ||||
|                     "Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); | ||||
|             exactlyTheseMembershipsAreReturned( | ||||
|                     result, | ||||
|                     "Membership(M-1000202, P-10002, [2022-10-01,2026-01-01), ACTIVE)"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -233,7 +263,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|         public void globalAdmin_canUpdateValidityOfArbitraryMembership() { | ||||
|             // given | ||||
|             context("superuser-alex@hostsharing.net"); | ||||
|             final var givenMembership =  givenSomeTemporaryMembership("First", "11"); | ||||
|             final var givenMembership = givenSomeTemporaryMembership("First", "11"); | ||||
|             assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership); | ||||
|             final var newValidityEnd = LocalDate.now(); | ||||
|  | ||||
| @@ -273,7 +303,8 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|             }); | ||||
|  | ||||
|             // then | ||||
|             result.assertExceptionWithRootCauseMessage(JpaSystemException.class, | ||||
|             result.assertExceptionWithRootCauseMessage( | ||||
|                     JpaSystemException.class, | ||||
|                     "[403] Subject ", " is not allowed to update hs_office.membership uuid"); | ||||
|         } | ||||
|  | ||||
| @@ -381,14 +412,16 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | ||||
|                 "[creating Membership test-data, hs_office.membership, INSERT, 03]"); | ||||
|     } | ||||
|  | ||||
|     private HsOfficeMembershipEntity givenSomeTemporaryMembership(final String partnerTradeName, final String memberNumberSuffix) { | ||||
|     private HsOfficeMembershipEntity givenSomeTemporaryMembership( | ||||
|             final String partnerTradeName, | ||||
|             final String memberNumberSuffix) { | ||||
|         return jpaAttempt.transacted(() -> { | ||||
|             context("superuser-alex@hostsharing.net"); | ||||
|             final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerTradeName).get(0); | ||||
|             final var newMembership = HsOfficeMembershipEntity.builder() | ||||
|                     .memberNumberSuffix(memberNumberSuffix) | ||||
|                     .partner(givenPartner) | ||||
|                     .validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) | ||||
|                     .validity(Range.closedInfinite(LocalDate.parse("2025-02-01"))) | ||||
|                     .membershipFeeBillable(true) | ||||
|                     .build(); | ||||
|  | ||||
|   | ||||
| @@ -287,12 +287,12 @@ class HsOfficeScenarioTests extends ScenarioTest { | ||||
|  | ||||
|         @Test | ||||
|         @Order(2011) | ||||
|         @Requires("Person: Test AG") | ||||
|         @Produces("Debitor: D-3101001 - Test AG - main debitor") | ||||
|         void shouldCreateExternalDebitorForPartner() { | ||||
|             new CreateExternalDebitorForPartner(scenarioTest) | ||||
|         @Requires("Debitor: D-3101000 - Test AG - main debitor") | ||||
|         @Produces("Debitor: D-3101001 - Test AG - additional debitor") | ||||
|         void shouldCreateAdditionDebitorForPartner() { | ||||
|             new CreateSelfDebitorForPartner(scenarioTest) | ||||
|                     .given("partnerPersonTradeName", "Test AG") | ||||
|                     .given("billingContactCaption", "Billing GmbH - billing department") | ||||
|                     .given("billingContactCaption", "Test AG - billing department") | ||||
|                     .given("billingContactEmailAddress", "billing@test-ag.example.org") | ||||
|                     .given("debitorNumberSuffix", "01") | ||||
|                     .given("billable", true) | ||||
| @@ -305,10 +305,30 @@ class HsOfficeScenarioTests extends ScenarioTest { | ||||
|                     .keep(); | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
|         @Order(2012) | ||||
|         @Requires("Person: Test AG") | ||||
|         @Produces("Debitor: D-3101002 - Test AG - external debitor") | ||||
|         void shouldCreateExternalDebitorForPartner() { | ||||
|             new CreateExternalDebitorForPartner(scenarioTest) | ||||
|                     .given("partnerPersonTradeName", "Test AG") | ||||
|                     .given("billingContactCaption", "Billing GmbH - billing department") | ||||
|                     .given("billingContactEmailAddress", "billing@test-ag.example.org") | ||||
|                     .given("debitorNumberSuffix", "02") | ||||
|                     .given("billable", true) | ||||
|                     .given("vatId", "VAT123456") | ||||
|                     .given("vatCountryCode", "DE") | ||||
|                     .given("vatBusiness", true) | ||||
|                     .given("vatReverseCharge", false) | ||||
|                     .given("defaultPrefix", "tsy") | ||||
|                     .doRun() | ||||
|                     .keep(); | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
|         @Order(2020) | ||||
|         @Requires("Person: Test AG") | ||||
|         @Produces(explicitly = "Debitor: D-3101000 - Test AG - delete debitor", permanent = false) | ||||
|         @Produces(explicitly = "Debitor: D-3101002 - Test AG - delete debitor", permanent = false) | ||||
|         void shouldDeleteDebitor() { | ||||
|             new DeleteDebitor(scenarioTest) | ||||
|                     .given("partnerNumber", "P-31020") | ||||
| @@ -317,7 +337,7 @@ class HsOfficeScenarioTests extends ScenarioTest { | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
|         @Order(2020) | ||||
|         @Order(2021) | ||||
|         @Requires("Debitor: D-3101000 - Test AG - main debitor") | ||||
|         @Disabled("see TODO.spec in DontDeleteDefaultDebitor") | ||||
|         void shouldNotDeleteDefaultDebitor() { | ||||
| @@ -387,7 +407,7 @@ class HsOfficeScenarioTests extends ScenarioTest { | ||||
|         void shouldCreateMembershipForPartner() { | ||||
|             new CreateMembership(scenarioTest) | ||||
|                     .given("partnerName", "Test AG") | ||||
|                     .given("validFrom", "2024-10-15") | ||||
|                     .given("validFrom", "2020-10-15") | ||||
|                     .given("newStatus", "ACTIVE") | ||||
|                     .given("membershipFeeBillable", "true") | ||||
|                     .doRun() | ||||
| @@ -395,14 +415,31 @@ class HsOfficeScenarioTests extends ScenarioTest { | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
|         @Order(4090) | ||||
|         @Order(4080) | ||||
|         @Requires("Membership: M-3101000 - Test AG") | ||||
|         @Produces("Membership: M-3101000 - Test AG - cancelled") | ||||
|         void shouldCancelMembershipOfPartner() { | ||||
|             new CancelMembership(scenarioTest) | ||||
|                     .given("memberNumber", "M-3101000") | ||||
|                     .given("validTo", "2025-12-30") | ||||
|                     .given("validTo", "2023-12-31") | ||||
|                     .given("newStatus", "CANCELLED") | ||||
|                     .doRun(); | ||||
|                     .doRun() | ||||
|                     .keep(); | ||||
|         } | ||||
|  | ||||
|         @Test | ||||
|         @Order(4090) | ||||
|         @Requires("Membership: M-3101000 - Test AG - cancelled") | ||||
|         @Produces("Membership: M-3101001 - Test AG") | ||||
|         void shouldCreateSubsequentMembershipOfPartner() { | ||||
|             new CreateMembership(scenarioTest) | ||||
|                     .given("partnerName", "Test AG") | ||||
|                     .given("memberNumberSuffix", "01") | ||||
|                     .given("validFrom", "2025-02-24") | ||||
|                     .given("newStatus", "ACTIVE") | ||||
|                     .given("membershipFeeBillable", "true") | ||||
|                     .doRun() | ||||
|                     .keep(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,7 @@ public class DeleteDebitor extends UseCase<DeleteDebitor> { | ||||
|                 .given("vatCountryCode", "DE") | ||||
|                 .given("vatBusiness", true) | ||||
|                 .given("vatReverseCharge", false) | ||||
|                 .given("defaultPrefix", "tsy")); | ||||
|                 .given("defaultPrefix", "tsz")); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package net.hostsharing.hsadminng.rbac.test; | ||||
|  | ||||
| import org.assertj.core.api.ObjectAssert; | ||||
| import lombok.SneakyThrows; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.core.NestedExceptionUtils; | ||||
| import org.springframework.stereotype.Service; | ||||
| @@ -78,9 +78,9 @@ public class JpaAttempt { | ||||
|     public static class JpaResult<T> { | ||||
|  | ||||
|         private final T value; | ||||
|         private final RuntimeException exception; | ||||
|         private final Throwable exception; | ||||
|  | ||||
|         private JpaResult(final T value, final RuntimeException exception) { | ||||
|         private JpaResult(final T value, final Throwable exception) { | ||||
|             this.value = value; | ||||
|             this.exception = exception; | ||||
|         } | ||||
| @@ -93,7 +93,7 @@ public class JpaAttempt { | ||||
|             return new JpaResult<>(value, null); | ||||
|         } | ||||
|  | ||||
|         public static <T> JpaResult<T> forException(final RuntimeException exception) { | ||||
|         public static <T> JpaResult<T> forException(final Throwable exception) { | ||||
|             return new JpaResult<>(null, exception); | ||||
|         } | ||||
|  | ||||
| @@ -105,20 +105,23 @@ public class JpaAttempt { | ||||
|             return value; | ||||
|         } | ||||
|  | ||||
|         public ObjectAssert<T> assertThatResult() { | ||||
|             assertSuccessful(); | ||||
|             return assertThat(returnedValue()); | ||||
|         } | ||||
|  | ||||
|         public RuntimeException caughtException() { | ||||
|         public Throwable caughtException() { | ||||
|             return exception; | ||||
|         } | ||||
|  | ||||
|         @SuppressWarnings("unchecked") | ||||
|         public <E extends RuntimeException> E caughtException(final Class<E> expectedExceptionClass) { | ||||
|         public <E extends Throwable> E caughtException(final Class<E> expectedExceptionClass) { | ||||
|             //noinspection unchecked | ||||
|             return caughtException((E) exception, expectedExceptionClass); | ||||
|         } | ||||
|  | ||||
|         public static <E extends Throwable> E caughtException(final Throwable exception, final Class<E> expectedExceptionClass) { | ||||
|             if (expectedExceptionClass.isAssignableFrom(exception.getClass())) { | ||||
|                 //noinspection unchecked | ||||
|                 return (E) exception; | ||||
|             } | ||||
|             if(exception.getCause() != null && exception.getCause() != exception ) { | ||||
|                 return caughtException(exception.getCause(), expectedExceptionClass); | ||||
|             } | ||||
|             throw new AssertionError("expected " + expectedExceptionClass + " but got " + exception); | ||||
|         } | ||||
|  | ||||
| @@ -127,7 +130,7 @@ public class JpaAttempt { | ||||
|         } | ||||
|  | ||||
|         public void assertExceptionWithRootCauseMessage( | ||||
|                 final Class<? extends RuntimeException> expectedExceptionClass, | ||||
|                 final Class<? extends Throwable> expectedExceptionClass, | ||||
|                 final String... expectedRootCauseMessages) { | ||||
|             assertThat(wasSuccessful()).as("wasSuccessful").isFalse(); | ||||
|             final String firstRootCauseMessageLine = firstRootCauseMessageLineOf(caughtException(expectedExceptionClass)); | ||||
| @@ -136,11 +139,11 @@ public class JpaAttempt { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public JpaResult<T> reThrowException() { | ||||
|         @SneakyThrows | ||||
|         public void reThrowException() { | ||||
|             if (exception != null) { | ||||
|                 throw exception; | ||||
|             } | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public JpaResult<T> assumeSuccessful() { | ||||
| @@ -158,9 +161,9 @@ public class JpaAttempt { | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         private String firstRootCauseMessageLineOf(final RuntimeException exception) { | ||||
|         private String firstRootCauseMessageLineOf(final Throwable exception) { | ||||
|             final var rootCause = NestedExceptionUtils.getRootCause(exception); | ||||
|             return Optional.ofNullable(rootCause) | ||||
|             return Optional.ofNullable(rootCause != null ? rootCause : exception) | ||||
|                     .map(Throwable::getMessage) | ||||
|                     .map(message -> message.split("\\r|\\n|\\r\\n", 0)[0]) | ||||
|                     .orElse(null); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user