From d8dc5655f396be7f4c78933727614f25de90abf9 Mon Sep 17 00:00:00 2001 From: Timotheus Pokorra Date: Wed, 26 Mar 2025 12:17:49 +0100 Subject: [PATCH 1/2] add a constraint for table relation to check if the anchor of a debitor has a partner relation --- .../503-relation/5030-hs-office-relation.sql | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main/resources/db/changelog/5-hs-office/503-relation/5030-hs-office-relation.sql b/src/main/resources/db/changelog/5-hs-office/503-relation/5030-hs-office-relation.sql index 813c6565..80290067 100644 --- a/src/main/resources/db/changelog/5-hs-office/503-relation/5030-hs-office-relation.sql +++ b/src/main/resources/db/changelog/5-hs-office/503-relation/5030-hs-office-relation.sql @@ -32,6 +32,7 @@ create table if not exists hs_office.relation -- ============================================================================ --changeset michael.hoennig:hs-office-relation-unique-constraints endDelimiter:--// +--validCheckSum: 9:79e93a47a62e44c661cd8d414626e49d -- ---------------------------------------------------------------------------- CREATE UNIQUE INDEX unique_relation_with_mark @@ -49,6 +50,36 @@ CREATE UNIQUE INDEX unique_partner_relation --// +-- ===================================================================================== +--changeset timotheus.pokorra:hs-office-relation-debitor-anchor-CONSTRAINT endDelimiter:--// +-- ------------------------------------------------------------------------------------- + +-- +-- Name: relation_check_debitor_anchor_partner(RelationType, uuid); Type: FUNCTION; Schema: hs_office; Owner: test +-- + +CREATE FUNCTION hs_office.relation_check_debitor_anchor_partner(mytype hs_office.RelationType, debitoranchoruuid uuid) RETURNS boolean + LANGUAGE plpgsql + AS ' +declare + countPartner integer; +begin + if mytype = ''DEBITOR'' then + SELECT COUNT(*) FROM hs_office.relation r + WHERE r.type = ''PARTNER'' AND r.holderuuid = debitoranchoruuid + INTO countPartner; + if countPartner < 1 then + raise exception ''[400] invalid debitor relation: anchor person must have a PARTNER relation''; + end if; + end if; + return true; +end; '; + +ALTER TABLE hs_office.relation ADD CONSTRAINT check_debitor_anchor_person CHECK (hs_office.relation_check_debitor_anchor_partner(type, anchorUuid)); + +--// + + -- ============================================================================ --changeset michael.hoennig:hs-office-relation-MAIN-TABLE-JOURNAL endDelimiter:--// -- ---------------------------------------------------------------------------- From 38efe866af27b8aad2933cc75c6f5df296d7d37b Mon Sep 17 00:00:00 2001 From: Timotheus Pokorra Date: Tue, 15 Apr 2025 12:15:26 +0200 Subject: [PATCH 2/2] add Test canNotAddDebitorRelationWithAnchorThatIsNotAPartner for constraint --- ...RealRelationRepositoryIntegrationTest.java | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRealRelationRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRealRelationRepositoryIntegrationTest.java index 2f80a889..16fc6002 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRealRelationRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRealRelationRepositoryIntegrationTest.java @@ -1,7 +1,9 @@ package net.hostsharing.hsadminng.hs.office.relation; import net.hostsharing.hsadminng.context.Context; -import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacRepository; +import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; @@ -21,6 +23,8 @@ import java.util.UUID; import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.NATURAL_PERSON; import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.REPRESENTATIVE; +import static net.hostsharing.hsadminng.rbac.test.EntityList.one; +import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest @@ -29,10 +33,13 @@ import static org.assertj.core.api.Assertions.assertThat; class HsOfficeRealRelationRepositoryIntegrationTest extends ContextBasedTestWithCleanup { @Autowired - HsOfficeRelationRealRepository relationRealRepo; + HsOfficeRelationRealRepository realRelationRepo; @Autowired - HsOfficePersonRbacRepository personRepo; + HsOfficePersonRealRepository realPersonRepo; + + @Autowired + HsOfficeContactRealRepository realContactRepo; @PersistenceContext EntityManager em; @@ -49,7 +56,7 @@ class HsOfficeRealRelationRepositoryIntegrationTest extends ContextBasedTestWith final var personUuid = determinePersonUuid(NATURAL_PERSON, "Smith"); // when - final var result = relationRealRepo.findRelationRelatedToPersonUuid(personUuid); + final var result = realRelationRepo.findRelationRelatedToPersonUuid(personUuid); // then context("superuser-alex@hostsharing.net"); // just to be able to access RBAc-entities persons+contact @@ -68,7 +75,7 @@ class HsOfficeRealRelationRepositoryIntegrationTest extends ContextBasedTestWith final var personUuid = determinePersonUuid(NATURAL_PERSON, "Smith"); // when: - final var result = relationRealRepo.findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData(personUuid, REPRESENTATIVE, null, null, null); + final var result = realRelationRepo.findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData(personUuid, REPRESENTATIVE, null, null, null); // then: context("superuser-alex@hostsharing.net"); // just to be able to access RBAc-entities persons+contact @@ -79,6 +86,34 @@ class HsOfficeRealRelationRepositoryIntegrationTest extends ContextBasedTestWith } } + @Nested + class CreateRelation { + + @Test + public void canNotAddDebitorRelationWithAnchorThatIsNotAPartner() { + // given + context("superuser-alex@hostsharing.net"); + + final var givenPartnerPerson = hsOfficePersonRealEntity("Non-Partner Person"); + final var givenContact = one(realContactRepo.findContactByOptionalCaptionLike("eleventh contact")); + + // when + final var result = attempt(em, () -> { + final var newRelation = HsOfficeRelationRealEntity.builder() + .type(HsOfficeRelationType.DEBITOR) + .anchor(givenPartnerPerson) + .holder(givenPartnerPerson) + .contact(givenContact) + .build(); + final var entity = realRelationRepo.save(newRelation); + return toCleanup(entity.load()); + }); + + // then + result.assertExceptionWithRootCauseMessage(org.postgresql.util.PSQLException.class, "ERROR: [400] invalid debitor relation: anchor person must have a PARTNER relation"); + } + } + private UUID determinePersonUuid(final HsOfficePersonType type, final String familyName) { return (UUID) em.createNativeQuery(""" SELECT uuid FROM hs_office.person p @@ -97,4 +132,12 @@ class HsOfficeRealRelationRepositoryIntegrationTest extends ContextBasedTestWith .extracting(HsOfficeRelation::toString) .containsExactlyInAnyOrder(relationNames); } + + public HsOfficePersonRealEntity hsOfficePersonRealEntity(final String tradeName) { + return realPersonRepo.save(HsOfficePersonRealEntity.builder() + .personType(HsOfficePersonType.NATURAL_PERSON) + .tradeName(tradeName) + .build()); + } + }