From 4811c0328c149cfdf2bc4a8f1b5aeeeefb1e3928 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 27 Sep 2024 11:00:58 +0200 Subject: [PATCH] fix Error code 500 in Relation.find without type (NullPointerException) (#109) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/109 Reviewed-by: Marc Sandlus --- .../office/person/HsOfficePersonEntity.java | 1 + .../relation/HsOfficeRelationController.java | 2 +- .../HsOfficeRelationRbacRepository.java | 6 +- .../HsOfficeRelationRealRepository.java | 2 +- ...RealRelationRepositoryIntegrationTest.java | 98 +++++++++++++++++++ ...fficeRelationControllerAcceptanceTest.java | 60 +++++++++++- ...ficeRelationRepositoryIntegrationTest.java | 4 +- 7 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRealRelationRepositoryIntegrationTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java index 6bd85c57..4d3e55ea 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java @@ -21,6 +21,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; +// TODO.refa: split HsOfficePersonEntity into Real+Rbac-Entity @Entity @Table(schema = "hs_office", name = "person_rv") @Getter diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java index b93537d9..22a113f0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java @@ -52,7 +52,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { context.define(currentSubject, assumedRoles); final var entities = relationRbacRepo.findRelationRelatedToPersonUuidAndRelationType(personUuid, - mapper.map(relationType, HsOfficeRelationType.class)); + relationType == null ? null : HsOfficeRelationType.valueOf(relationType.name())); final var resources = mapper.mapList(entities, HsOfficeRelationResource.class, RELATION_ENTITY_TO_RESOURCE_POSTMAPPER); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRbacRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRbacRepository.java index 3c89a0b7..ec9aea59 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRbacRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRbacRepository.java @@ -13,20 +13,20 @@ public interface HsOfficeRelationRbacRepository extends Repository findByUuid(UUID id); default List findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) { - return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType.toString()); + return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType == null ? null : relationType.toString()); } @Query(value = """ SELECT p.* FROM hs_office.relation_rv AS p WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid - """, nativeQuery = true) + """, nativeQuery = true) List findRelationRelatedToPersonUuid(@NotNull UUID personUuid); @Query(value = """ SELECT p.* FROM hs_office.relation_rv AS p WHERE (:relationType IS NULL OR p.type = cast(:relationType AS hs_office.RelationType)) AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid) - """, nativeQuery = true) + """, nativeQuery = true) List findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType); HsOfficeRelationRbacEntity save(final HsOfficeRelationRbacEntity entity); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRealRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRealRepository.java index 9cf58b86..292f9034 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRealRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRealRepository.java @@ -13,7 +13,7 @@ public interface HsOfficeRelationRealRepository extends Repository findByUuid(UUID id); default List findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) { - return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType.toString()); + return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType == null ? null : relationType.toString()); } @Query(value = """ 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 new file mode 100644 index 00000000..5c912263 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRealRelationRepositoryIntegrationTest.java @@ -0,0 +1,98 @@ +package net.hostsharing.hsadminng.hs.office.relation; + +import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; +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.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.http.HttpServletRequest; +import java.util.List; +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 org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@Import( { Context.class, JpaAttempt.class }) +class HsOfficeRealRelationRepositoryIntegrationTest extends ContextBasedTestWithCleanup { + + @Autowired + HsOfficeRelationRealRepository relationRealRepo; + + @Autowired + HsOfficePersonRepository personRepo; + + @PersistenceContext + EntityManager em; + + @MockBean + HttpServletRequest request; + + @Nested + class FindRelations { + + @Test + public void canFindAllRelationsOfGivenPerson() { + // given + final var personUuid = determinePersonUuid(NATURAL_PERSON, "Smith"); + + // when + final var result = relationRealRepo.findRelationRelatedToPersonUuidAndRelationType(personUuid, null); + + // then + context("superuser-alex@hostsharing.net"); // just to be able to access RBAc-entities persons+contact + exactlyTheseRelationsAreReturned( + result, + "rel(anchor='LP Second e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')", + "rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Smith, Peter', contact='sixth contact')", + "rel(anchor='NP Smith, Peter', type='DEBITOR', holder='NP Smith, Peter', contact='third contact')", + "rel(anchor='IF Third OHG', type='SUBSCRIBER', mark='members-announce', holder='NP Smith, Peter', contact='third contact')" + ); + } + + @Test + public void canFindAllRelationsOfGivenPersonAndType() { + // given: + final var personUuid = determinePersonUuid(NATURAL_PERSON, "Smith"); + + // when: + final var result = relationRealRepo.findRelationRelatedToPersonUuidAndRelationType(personUuid, REPRESENTATIVE); + + // then: + context("superuser-alex@hostsharing.net"); // just to be able to access RBAc-entities persons+contact + exactlyTheseRelationsAreReturned( + result, + "rel(anchor='LP Second e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')" + ); + } + } + + private UUID determinePersonUuid(final HsOfficePersonType type, final String familyName) { + return (UUID) em.createNativeQuery(""" + SELECT uuid FROM hs_office.person p + WHERE p.personType = cast(:type as hs_office.PersonType) AND p.familyName = :familyName + """, UUID.class) + .setParameter("familyName", familyName) + .setParameter("type", type.toString()) + .getSingleResult(); + + } + + private void exactlyTheseRelationsAreReturned( + final List actualResult, + final String... relationNames) { + assertThat(actualResult) + .extracting(HsOfficeRelation::toString) + .containsExactlyInAnyOrder(relationNames); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java index 25e1629e..23e8410b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java @@ -55,7 +55,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean class ListRelations { @Test - void globalAdmin_withoutAssumedRoles_canViewAllRelationsOfGivenPersonAndType_ifNoCriteriaGiven() throws JSONException { + void globalAdmin_withoutAssumedRoles_canViewAllRelationsOfGivenPersonAndType() throws JSONException { // given context.define("superuser-alex@hostsharing.net"); @@ -111,6 +111,64 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean """)); // @formatter:on } + + @Test + void personAdmin_canViewAllRelationsOfGivenRelatedPersonAndAnyType() throws JSONException { + + // given + context.define("contact-admin@firstcontact.example.com"); + final var givenPerson = personRepo.findPersonByOptionalNameLike("First GmbH").get(0); + + RestAssured // @formatter:off + .given() + .header("current-subject", "superuser-alex@hostsharing.net") + .port(port) + .when() + .get("http://localhost/api/hs/office/relations?personUuid=%s" + .formatted(givenPerson.getUuid(), HsOfficeRelationTypeResource.PARTNER)) + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + [ + { + "anchor": { + "tradeName": "First GmbH" + }, + "holder": { + "givenName": "Susan", + "familyName": "Firby" + }, + "type": "REPRESENTATIVE", + "mark": null, + "contact": { "caption": "first contact" } + }, + { + "anchor": { + "tradeName": "Hostsharing eG" + }, + "holder": { + "tradeName": "First GmbH" + }, + "type": "PARTNER", + "mark": null, + "contact": { "caption": "first contact" } + }, + { + "anchor": { + "tradeName": "First GmbH" + }, + "holder": { + "tradeName": "First GmbH" + }, + "type": "DEBITOR", + "mark": null, + "contact": { "caption": "first contact" } + } + ] + """)); + // @formatter:on + } } @Nested diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java index aa5d54d8..ffba5c42 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java @@ -163,7 +163,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea } @Nested - class FindAllRelations { + class FindRelations { @Test public void globalAdmin_withoutAssumedRole_canViewAllRelationsOfArbitraryPerson() { @@ -193,7 +193,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea .findFirst().orElseThrow(); // when: - final var result = relationRbacRepo.findRelationRelatedToPersonUuid(person.getUuid()); + final var result = relationRbacRepo.findRelationRelatedToPersonUuidAndRelationType(person.getUuid(), null); // then: exactlyTheseRelationsAreReturned(