multiple debitors and subsequent memberships

This commit is contained in:
Michael Hoennig 2025-02-24 19:34:30 +01:00
parent a0635960a5
commit fb216c4769
7 changed files with 154 additions and 60 deletions

View File

@ -32,6 +32,36 @@ 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 $$
BEGIN
IF EXISTS (
SELECT 1
FROM hs_office.membership
WHERE partnerUuid = NEW.partnerUuid
AND uuid <> NEW.uuid
AND NEW.validity && validity
) THEN
RAISE EXCEPTION 'Membership validity ranges overlap for partnerUuid %', NEW.partnerUuid;
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:--// --changeset michael.hoennig:hs-office-membership-MAIN-TABLE-JOURNAL endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------

View File

@ -10,7 +10,8 @@
*/ */
create or replace procedure hs_office.membership_create_test_data( create or replace procedure hs_office.membership_create_test_data(
forPartnerNumber numeric(5), forPartnerNumber numeric(5),
newMemberNumberSuffix char(2) ) newMemberNumberSuffix char(2),
validity daterange)
language plpgsql as $$ language plpgsql as $$
declare declare
relatedPartner hs_office.partner; relatedPartner hs_office.partner;
@ -22,7 +23,7 @@ begin
raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner; raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner;
insert insert
into hs_office.membership (uuid, partneruuid, memberNumberSuffix, validity, status) 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; $$; end; $$;
--// --//
@ -35,9 +36,9 @@ do language plpgsql $$
begin begin
call base.defineContext('creating Membership test-data', null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN'); 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(10001, '01', daterange('20221001' , '20241231', '[)'));
call hs_office.membership_create_test_data(10002, '02'); call hs_office.membership_create_test_data(10002, '02', daterange('20221001' , '20251231', '[]'));
call hs_office.membership_create_test_data(10003, '03'); call hs_office.membership_create_test_data(10003, '03', daterange('20221001' , null, '[]'));
end; end;
$$; $$;
--// --//

View File

@ -86,7 +86,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
"memberNumber": "M-1000101", "memberNumber": "M-1000101",
"memberNumberSuffix": "01", "memberNumberSuffix": "01",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": "2024-12-30",
"status": "ACTIVE" "status": "ACTIVE"
}, },
{ {
@ -94,7 +94,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
"memberNumber": "M-1000202", "memberNumber": "M-1000202",
"memberNumberSuffix": "02", "memberNumberSuffix": "02",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": "2025-12-31",
"status": "ACTIVE" "status": "ACTIVE"
}, },
{ {
@ -133,7 +133,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
"memberNumber": "M-1000101", "memberNumber": "M-1000101",
"memberNumberSuffix": "01", "memberNumberSuffix": "01",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": "2024-12-30",
"status": "ACTIVE" "status": "ACTIVE"
} }
] ]
@ -161,7 +161,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
"memberNumber": "M-1000202", "memberNumber": "M-1000202",
"memberNumberSuffix": "02", "memberNumberSuffix": "02",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": "2025-12-31",
"status": "ACTIVE" "status": "ACTIVE"
} }
] ]
@ -177,7 +177,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
void globalAdmin_canAddMembership() { void globalAdmin_canAddMembership() {
context.define("superuser-alex@hostsharing.net"); 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 givenMemberSuffix = TEMP_MEMBER_NUMBER_SUFFIX;
final var expectedMemberNumber = Integer.parseInt(givenPartner.getPartnerNumber() + 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", "partner.uuid": "%s",
"memberNumberSuffix": "%s", "memberNumberSuffix": "%s",
"validFrom": "2022-10-13", "validFrom": "2025-02-13",
"membershipFeeBillable": "true" "membershipFeeBillable": "true"
} }
""".formatted(givenPartner.getUuid(), givenMemberSuffix)) """.formatted(givenPartner.getUuid(), givenMemberSuffix))
@ -200,10 +200,10 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.statusCode(201) .statusCode(201)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("partner.partnerNumber", is("P-10003")) .body("partner.partnerNumber", is("P-10001"))
.body("memberNumber", is("M-" + expectedMemberNumber)) .body("memberNumber", is("M-" + expectedMemberNumber))
.body("memberNumberSuffix", is(givenMemberSuffix)) .body("memberNumberSuffix", is(givenMemberSuffix))
.body("validFrom", is("2022-10-13")) .body("validFrom", is("2025-02-13"))
.body("validTo", equalTo(null)) .body("validTo", equalTo(null))
.header("Location", startsWith("http://localhost")) .header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on .extract().header("Location"); // @formatter:on
@ -239,7 +239,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
"memberNumber": "M-1000101", "memberNumber": "M-1000101",
"memberNumberSuffix": "01", "memberNumberSuffix": "01",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": "2024-12-30",
"status": "ACTIVE" "status": "ACTIVE"
} }
""")); // @formatter:on """)); // @formatter:on
@ -297,13 +297,13 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembershipBessler("First"); final var givenMembership = givenSomeTemporaryMembershipBessler("First");
final var location = RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-subject", "superuser-alex@hostsharing.net") .header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"validTo": "2023-12-31", "validTo": "2025-12-31",
"status": "CANCELLED" "status": "CANCELLED"
} }
""") """)
@ -316,8 +316,8 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("partner.partnerNumber", is("P-" + givenMembership.getPartner().getPartnerNumber())) .body("partner.partnerNumber", is("P-" + givenMembership.getPartner().getPartnerNumber()))
.body("memberNumberSuffix", is(givenMembership.getMemberNumberSuffix())) .body("memberNumberSuffix", is(givenMembership.getMemberNumberSuffix()))
.body("validFrom", is("2022-11-01")) .body("validFrom", is("2025-02-01"))
.body("validTo", is("2023-12-31")) .body("validTo", is("2025-12-31"))
.body("status", is("CANCELLED")); .body("status", is("CANCELLED"));
// @formatter:on // @formatter:on
@ -326,7 +326,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.matches(mandate -> { .matches(mandate -> {
assertThat(mandate.getPartner().toShortString()).isEqualTo("P-10001"); assertThat(mandate.getPartner().toShortString()).isEqualTo("P-10001");
assertThat(mandate.getMemberNumberSuffix()).isEqualTo(givenMembership.getMemberNumberSuffix()); 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); assertThat(mandate.getStatus()).isEqualTo(CANCELLED);
return true; return true;
}); });
@ -348,7 +348,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"validTo": "2024-01-01", "validTo": "2025-12-31",
"status": "CANCELLED" "status": "CANCELLED"
} }
""") """)
@ -361,7 +361,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
// finally, the Membership is actually updated // finally, the Membership is actually updated
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get()
.matches(mandate -> { .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); assertThat(mandate.getStatus()).isEqualTo(CANCELLED);
return true; return true;
}); });
@ -434,7 +434,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
final var newMembership = HsOfficeMembershipEntity.builder() final var newMembership = HsOfficeMembershipEntity.builder()
.partner(givenPartner) .partner(givenPartner)
.memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX) .memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX)
.validity(Range.closedInfinite(LocalDate.parse("2022-11-01"))) .validity(Range.closedInfinite(LocalDate.parse("2025-02-01")))
.status(ACTIVE) .status(ACTIVE)
.membershipFeeBillable(true) .membershipFeeBillable(true)
.build(); .build();

View File

@ -12,6 +12,7 @@ import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
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.postgresql.util.PSQLException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.bean.override.mockito.MockitoBean;
@ -74,7 +75,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
final var newMembership = HsOfficeMembershipEntity.builder() final var newMembership = HsOfficeMembershipEntity.builder()
.memberNumberSuffix("11") .memberNumberSuffix("11")
.partner(givenPartner) .partner(givenPartner)
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) .validity(Range.closedInfinite(LocalDate.parse("2025-01-01")))
.membershipFeeBillable(true) .membershipFeeBillable(true)
.build(); .build();
return toCleanup(membershipRepo.save(newMembership).load()); return toCleanup(membershipRepo.save(newMembership).load());
@ -87,6 +88,28 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
assertThat(membershipRepo.count()).isEqualTo(count + 1); assertThat(membershipRepo.count()).isEqualTo(count + 1);
} }
@Test
public void creatingMembershipForSamePartnerIsDisallowedIfAnotherOneIsStillActive() {
// given
context("superuser-alex@hostsharing.net");
final var count = membershipRepo.count();
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());
}
@Test @Test
public void createsAndGrantsRoles() { public void createsAndGrantsRoles() {
// given // given
@ -102,7 +125,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
final var newMembership = HsOfficeMembershipEntity.builder() final var newMembership = HsOfficeMembershipEntity.builder()
.memberNumberSuffix("17") .memberNumberSuffix("17")
.partner(givenPartner) .partner(givenPartner)
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) .validity(Range.closedInfinite(LocalDate.parse("2025-01-01")))
.membershipFeeBillable(true) .membershipFeeBillable(true)
.build(); .build();
return toCleanup(membershipRepo.save(newMembership)); return toCleanup(membershipRepo.save(newMembership));
@ -163,8 +186,8 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
// then // then
exactlyTheseMembershipsAreReturned( exactlyTheseMembershipsAreReturned(
result, result,
"Membership(M-1000101, P-10001, [2022-10-01,), ACTIVE)", "Membership(M-1000101, P-10001, [2022-10-01,2024-12-31), ACTIVE)",
"Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)", "Membership(M-1000202, P-10002, [2022-10-01,2026-01-01), ACTIVE)",
"Membership(M-1000303, P-10003, [2022-10-01,), ACTIVE)"); "Membership(M-1000303, P-10003, [2022-10-01,), ACTIVE)");
} }
@ -179,7 +202,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
// then // then
exactlyTheseMembershipsAreReturned(result, exactlyTheseMembershipsAreReturned(result,
"Membership(M-1000101, P-10001, [2022-10-01,), ACTIVE)"); "Membership(M-1000101, P-10001, [2022-10-01,2024-12-31), ACTIVE)");
} }
@Test @Test
@ -194,7 +217,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
assertThat(result) assertThat(result)
.isNotNull() .isNotNull()
.extracting(Object::toString) .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 @Test
@ -209,7 +232,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
assertThat(result) assertThat(result)
.isNotNull() .isNotNull()
.extracting(Object::toString) .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 @Test
@ -222,7 +245,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
// then // then
exactlyTheseMembershipsAreReturned(result, exactlyTheseMembershipsAreReturned(result,
"Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); "Membership(M-1000202, P-10002, [2022-10-01,2026-01-01), ACTIVE)");
} }
} }
@ -388,7 +411,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
final var newMembership = HsOfficeMembershipEntity.builder() final var newMembership = HsOfficeMembershipEntity.builder()
.memberNumberSuffix(memberNumberSuffix) .memberNumberSuffix(memberNumberSuffix)
.partner(givenPartner) .partner(givenPartner)
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) .validity(Range.closedInfinite(LocalDate.parse("2025-02-01")))
.membershipFeeBillable(true) .membershipFeeBillable(true)
.build(); .build();

View File

@ -287,12 +287,12 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Test @Test
@Order(2011) @Order(2011)
@Requires("Person: Test AG") @Requires("Debitor: D-3101000 - Test AG - main debitor")
@Produces("Debitor: D-3101001 - Test AG - main debitor") @Produces("Debitor: D-3101001 - Test AG - additional debitor")
void shouldCreateExternalDebitorForPartner() { void shouldCreateAdditionDebitorForPartner() {
new CreateExternalDebitorForPartner(scenarioTest) new CreateSelfDebitorForPartner(scenarioTest)
.given("partnerPersonTradeName", "Test AG") .given("partnerPersonTradeName", "Test AG")
.given("billingContactCaption", "Billing GmbH - billing department") .given("billingContactCaption", "Test AG - billing department")
.given("billingContactEmailAddress", "billing@test-ag.example.org") .given("billingContactEmailAddress", "billing@test-ag.example.org")
.given("debitorNumberSuffix", "01") .given("debitorNumberSuffix", "01")
.given("billable", true) .given("billable", true)
@ -305,10 +305,30 @@ class HsOfficeScenarioTests extends ScenarioTest {
.keep(); .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 @Test
@Order(2020) @Order(2020)
@Requires("Person: Test AG") @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() { void shouldDeleteDebitor() {
new DeleteDebitor(scenarioTest) new DeleteDebitor(scenarioTest)
.given("partnerNumber", "P-31020") .given("partnerNumber", "P-31020")
@ -317,7 +337,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
} }
@Test @Test
@Order(2020) @Order(2021)
@Requires("Debitor: D-3101000 - Test AG - main debitor") @Requires("Debitor: D-3101000 - Test AG - main debitor")
@Disabled("see TODO.spec in DontDeleteDefaultDebitor") @Disabled("see TODO.spec in DontDeleteDefaultDebitor")
void shouldNotDeleteDefaultDebitor() { void shouldNotDeleteDefaultDebitor() {
@ -387,7 +407,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldCreateMembershipForPartner() { void shouldCreateMembershipForPartner() {
new CreateMembership(scenarioTest) new CreateMembership(scenarioTest)
.given("partnerName", "Test AG") .given("partnerName", "Test AG")
.given("validFrom", "2024-10-15") .given("validFrom", "2020-10-15")
.given("newStatus", "ACTIVE") .given("newStatus", "ACTIVE")
.given("membershipFeeBillable", "true") .given("membershipFeeBillable", "true")
.doRun() .doRun()
@ -395,14 +415,31 @@ class HsOfficeScenarioTests extends ScenarioTest {
} }
@Test @Test
@Order(4090) @Order(4080)
@Requires("Membership: M-3101000 - Test AG") @Requires("Membership: M-3101000 - Test AG")
@Produces("Membership: M-3101000 - Test AG - cancelled")
void shouldCancelMembershipOfPartner() { void shouldCancelMembershipOfPartner() {
new CancelMembership(scenarioTest) new CancelMembership(scenarioTest)
.given("memberNumber", "M-3101000") .given("memberNumber", "M-3101000")
.given("validTo", "2025-12-30") .given("validTo", "2023-12-31")
.given("newStatus", "CANCELLED") .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();
} }
} }

View File

@ -19,7 +19,7 @@ public class DeleteDebitor extends UseCase<DeleteDebitor> {
.given("vatCountryCode", "DE") .given("vatCountryCode", "DE")
.given("vatBusiness", true) .given("vatBusiness", true)
.given("vatReverseCharge", false) .given("vatReverseCharge", false)
.given("defaultPrefix", "tsy")); .given("defaultPrefix", "tsz"));
} }
@Override @Override

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.rbac.test; 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.beans.factory.annotation.Autowired;
import org.springframework.core.NestedExceptionUtils; import org.springframework.core.NestedExceptionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -78,9 +78,9 @@ public class JpaAttempt {
public static class JpaResult<T> { public static class JpaResult<T> {
private final T value; 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.value = value;
this.exception = exception; this.exception = exception;
} }
@ -93,7 +93,7 @@ public class JpaAttempt {
return new JpaResult<>(value, null); 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); return new JpaResult<>(null, exception);
} }
@ -105,20 +105,23 @@ public class JpaAttempt {
return value; return value;
} }
public ObjectAssert<T> assertThatResult() { public Throwable caughtException() {
assertSuccessful();
return assertThat(returnedValue());
}
public RuntimeException caughtException() {
return exception; return exception;
} }
@SuppressWarnings("unchecked") public <E extends Throwable> E caughtException(final Class<E> expectedExceptionClass) {
public <E extends RuntimeException> 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())) { if (expectedExceptionClass.isAssignableFrom(exception.getClass())) {
//noinspection unchecked
return (E) exception; return (E) exception;
} }
if(exception.getCause() != null && exception.getCause() != exception ) {
return caughtException(exception.getCause(), expectedExceptionClass);
}
throw new AssertionError("expected " + expectedExceptionClass + " but got " + exception); throw new AssertionError("expected " + expectedExceptionClass + " but got " + exception);
} }
@ -127,7 +130,7 @@ public class JpaAttempt {
} }
public void assertExceptionWithRootCauseMessage( public void assertExceptionWithRootCauseMessage(
final Class<? extends RuntimeException> expectedExceptionClass, final Class<? extends Throwable> expectedExceptionClass,
final String... expectedRootCauseMessages) { final String... expectedRootCauseMessages) {
assertThat(wasSuccessful()).as("wasSuccessful").isFalse(); assertThat(wasSuccessful()).as("wasSuccessful").isFalse();
final String firstRootCauseMessageLine = firstRootCauseMessageLineOf(caughtException(expectedExceptionClass)); final String firstRootCauseMessageLine = firstRootCauseMessageLineOf(caughtException(expectedExceptionClass));
@ -136,11 +139,11 @@ public class JpaAttempt {
} }
} }
public JpaResult<T> reThrowException() { @SneakyThrows
public void reThrowException() {
if (exception != null) { if (exception != null) {
throw exception; throw exception;
} }
return this;
} }
public JpaResult<T> assumeSuccessful() { public JpaResult<T> assumeSuccessful() {
@ -158,9 +161,9 @@ public class JpaAttempt {
return this; return this;
} }
private String firstRootCauseMessageLineOf(final RuntimeException exception) { private String firstRootCauseMessageLineOf(final Throwable exception) {
final var rootCause = NestedExceptionUtils.getRootCause(exception); final var rootCause = NestedExceptionUtils.getRootCause(exception);
return Optional.ofNullable(rootCause) return Optional.ofNullable(rootCause != null ? rootCause : exception)
.map(Throwable::getMessage) .map(Throwable::getMessage)
.map(message -> message.split("\\r|\\n|\\r\\n", 0)[0]) .map(message -> message.split("\\r|\\n|\\r\\n", 0)[0])
.orElse(null); .orElse(null);