split PersonEntity/Repo into Rbac and Real Entity/Repo and use in Relation for faster lazy loading #130

Merged
hsh-michaelhoennig merged 15 commits from feature/split-PersonEntity-and-Repo-into-Rbac-and-Real into master 2024-12-05 10:39:26 +01:00
5 changed files with 225 additions and 32 deletions
Showing only changes of commit 5eb1fd4085 - Show all commits

View File

@ -14,7 +14,7 @@ public interface HsOfficePersonRealRepository extends Repository<HsOfficePersonR
Optional<HsOfficePersonRealEntity> findByUuid(UUID personUuid); Optional<HsOfficePersonRealEntity> findByUuid(UUID personUuid);
@Query(""" @Query("""
SELECT p FROM HsOfficePersonRbacEntity p SELECT p FROM HsOfficePersonRealEntity p
WHERE :name is null WHERE :name is null
OR p.tradeName like concat(cast(:name as text), '%') OR p.tradeName like concat(cast(:name as text), '%')
OR p.givenName like concat(cast(:name as text), '%') OR p.givenName like concat(cast(:name as text), '%')
@ -26,9 +26,6 @@ public interface HsOfficePersonRealRepository extends Repository<HsOfficePersonR
@Timed("app.office.persons.repo.save.rbac") @Timed("app.office.persons.repo.save.rbac")
HsOfficePersonRealEntity save(final HsOfficePersonRealEntity entity); HsOfficePersonRealEntity save(final HsOfficePersonRealEntity entity);
@Timed("app.office.persons.repo.deleteByUuid.rbac")
int deleteByUuid(final UUID personUuid);
@Timed("app.office.persons.repo.count.rbac") @Timed("app.office.persons.repo.count.rbac")
long count(); long count();
} }

View File

@ -21,7 +21,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
import static net.hostsharing.hsadminng.hs.office.person.TestHsOfficePerson.hsOfficePerson; import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacTestEntity.hsOfficePersonRbacEntity;
import static net.hostsharing.hsadminng.rbac.grant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.grant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.role.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.role.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
@ -29,10 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest @DataJpaTest
@Import( { Context.class, JpaAttempt.class }) @Import( { Context.class, JpaAttempt.class })
class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanup { class HsOfficePersonRbacRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
@Autowired @Autowired
HsOfficePersonRbacRepository personRepo; HsOfficePersonRbacRepository personRbacRepo;
@Autowired @Autowired
RawRbacRoleRepository rawRoleRepo; RawRbacRoleRepository rawRoleRepo;
@ -56,34 +56,34 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
public void globalAdmin_withoutAssumedRole_canCreateNewPerson() { public void globalAdmin_withoutAssumedRole_canCreateNewPerson() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = personRepo.count(); final var count = personRbacRepo.count();
// when // when
final var result = attempt(em, () -> toCleanup(personRepo.save( final var result = attempt(em, () -> toCleanup(personRbacRepo.save(
hsOfficePerson("a new person")))); hsOfficePersonRbacEntity("a new person"))));
// then // then
result.assertSuccessful(); result.assertSuccessful();
assertThat(result.returnedValue()).isNotNull().extracting(HsOfficePersonRbacEntity::getUuid).isNotNull(); assertThat(result.returnedValue()).isNotNull().extracting(HsOfficePersonRbacEntity::getUuid).isNotNull();
assertThatPersonIsPersisted(result.returnedValue()); assertThatPersonIsPersisted(result.returnedValue());
assertThat(personRepo.count()).isEqualTo(count + 1); assertThat(personRbacRepo.count()).isEqualTo(count + 1);
} }
@Test @Test
public void arbitraryUser_canCreateNewPerson() { public void arbitraryUser_canCreateNewPerson() {
// given // given
context("selfregistered-user-drew@hostsharing.org"); context("selfregistered-user-drew@hostsharing.org");
final var count = personRepo.count(); final var count = personRbacRepo.count();
// when // when
final var result = attempt(em, () -> toCleanup(personRepo.save( final var result = attempt(em, () -> toCleanup(personRbacRepo.save(
hsOfficePerson("another new person")))); hsOfficePersonRbacEntity("another new person"))));
// then // then
result.assertSuccessful(); result.assertSuccessful();
assertThat(result.returnedValue()).isNotNull().extracting(HsOfficePersonRbacEntity::getUuid).isNotNull(); assertThat(result.returnedValue()).isNotNull().extracting(HsOfficePersonRbacEntity::getUuid).isNotNull();
assertThatPersonIsPersisted(result.returnedValue()); assertThatPersonIsPersisted(result.returnedValue());
assertThat(personRepo.count()).isEqualTo(count + 1); assertThat(personRbacRepo.count()).isEqualTo(count + 1);
} }
@Test @Test
@ -95,7 +95,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// when // when
attempt(em, () -> toCleanup( attempt(em, () -> toCleanup(
personRepo.save(hsOfficePerson("another new person")) personRbacRepo.save(hsOfficePersonRbacEntity("another new person"))
)).assumeSuccessful(); )).assumeSuccessful();
// then // then
@ -123,7 +123,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
} }
private void assertThatPersonIsPersisted(final HsOfficePersonRbacEntity saved) { private void assertThatPersonIsPersisted(final HsOfficePersonRbacEntity saved) {
final var found = personRepo.findByUuid(saved.getUuid()); final var found = personRbacRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString()); assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
} }
} }
@ -137,7 +137,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
// when // when
final var result = personRepo.findPersonByOptionalNameLike(null); final var result = personRbacRepo.findPersonByOptionalNameLike(null);
// then // then
allThesePersonsAreReturned( allThesePersonsAreReturned(
@ -155,7 +155,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// when: // when:
context("pac-admin-zzz00@zzz.example.com"); context("pac-admin-zzz00@zzz.example.com");
final var result = personRepo.findPersonByOptionalNameLike(null); final var result = personRbacRepo.findPersonByOptionalNameLike(null);
// then: // then:
exactlyThesePersonsAreReturned(result, givenPerson.getTradeName()); exactlyThesePersonsAreReturned(result, givenPerson.getTradeName());
@ -171,7 +171,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
// when // when
final var result = personRepo.findPersonByOptionalNameLike("Second"); final var result = personRbacRepo.findPersonByOptionalNameLike("Second");
// then // then
exactlyThesePersonsAreReturned(result, "Second e.K."); exactlyThesePersonsAreReturned(result, "Second e.K.");
@ -184,7 +184,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// when: // when:
context("selfregistered-user-drew@hostsharing.org"); context("selfregistered-user-drew@hostsharing.org");
final var result = personRepo.findPersonByOptionalNameLike(givenPerson.getTradeName()); final var result = personRbacRepo.findPersonByOptionalNameLike(givenPerson.getTradeName());
// then: // then:
exactlyThesePersonsAreReturned(result, givenPerson.getTradeName()); exactlyThesePersonsAreReturned(result, givenPerson.getTradeName());
@ -202,14 +202,14 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
personRepo.deleteByUuid(givenPerson.getUuid()); personRbacRepo.deleteByUuid(givenPerson.getUuid());
}); });
// then // then
result.assertSuccessful(); result.assertSuccessful();
assertThat(jpaAttempt.transacted(() -> { assertThat(jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
return personRepo.findPersonByOptionalNameLike(givenPerson.getTradeName()); return personRbacRepo.findPersonByOptionalNameLike(givenPerson.getTradeName());
}).assertSuccessful().returnedValue()).hasSize(0); }).assertSuccessful().returnedValue()).hasSize(0);
} }
@ -221,14 +221,14 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("selfregistered-user-drew@hostsharing.org", null); context("selfregistered-user-drew@hostsharing.org", null);
personRepo.deleteByUuid(givenPerson.getUuid()); personRbacRepo.deleteByUuid(givenPerson.getUuid());
}); });
// then // then
result.assertSuccessful(); result.assertSuccessful();
assertThat(jpaAttempt.transacted(() -> { assertThat(jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
return personRepo.findPersonByOptionalNameLike(givenPerson.getTradeName()); return personRbacRepo.findPersonByOptionalNameLike(givenPerson.getTradeName());
}).assertSuccessful().returnedValue()).hasSize(0); }).assertSuccessful().returnedValue()).hasSize(0);
} }
@ -245,7 +245,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("selfregistered-user-drew@hostsharing.org", null); context("selfregistered-user-drew@hostsharing.org", null);
return personRepo.deleteByUuid(givenPerson.getUuid()); return personRbacRepo.deleteByUuid(givenPerson.getUuid());
}); });
// then // then
@ -281,13 +281,13 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
Supplier<HsOfficePersonRbacEntity> entitySupplier) { Supplier<HsOfficePersonRbacEntity> entitySupplier) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context(createdByUser); context(createdByUser);
return toCleanup(personRepo.save(entitySupplier.get())); return toCleanup(personRbacRepo.save(entitySupplier.get()));
}).assumeSuccessful().returnedValue(); }).assumeSuccessful().returnedValue();
} }
private HsOfficePersonRbacEntity givenSomeTemporaryPerson(final String createdByUser) { private HsOfficePersonRbacEntity givenSomeTemporaryPerson(final String createdByUser) {
return givenSomeTemporaryPerson(createdByUser, () -> return givenSomeTemporaryPerson(createdByUser, () ->
hsOfficePerson("some temporary person #" + RandomStringUtils.random(12))); hsOfficePersonRbacEntity("some temporary person #" + RandomStringUtils.random(12)));
} }
void exactlyThesePersonsAreReturned(final List<HsOfficePersonRbacEntity> actualResult, final String... personCaptions) { void exactlyThesePersonsAreReturned(final List<HsOfficePersonRbacEntity> actualResult, final String... personCaptions) {
@ -298,7 +298,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
void allThesePersonsAreReturned(final List<HsOfficePersonRbacEntity> actualResult, final String... personCaptions) { void allThesePersonsAreReturned(final List<HsOfficePersonRbacEntity> actualResult, final String... personCaptions) {
assertThat(actualResult) assertThat(actualResult)
.extracting(hsOfficePersonEntity -> hsOfficePersonEntity.toShortString()) .extracting(HsOfficePerson::toShortString)
.contains(personCaptions); .contains(personCaptions);
} }
} }

View File

@ -1,11 +1,11 @@
package net.hostsharing.hsadminng.hs.office.person; package net.hostsharing.hsadminng.hs.office.person;
public class TestHsOfficePerson { public class HsOfficePersonRbacTestEntity {
public static final HsOfficePersonRbacEntity somePerson = hsOfficePerson("some person"); public static final HsOfficePersonRbacEntity somePerson = hsOfficePersonRbacEntity("some person");
static public HsOfficePersonRbacEntity hsOfficePerson(final String tradeName) { public static HsOfficePersonRbacEntity hsOfficePersonRbacEntity(final String tradeName) {
return HsOfficePersonRbacEntity.builder() return HsOfficePersonRbacEntity.builder()
.personType(HsOfficePersonType.NATURAL_PERSON) .personType(HsOfficePersonType.NATURAL_PERSON)
.tradeName(tradeName) .tradeName(tradeName)

View File

@ -0,0 +1,182 @@
package net.hostsharing.hsadminng.hs.office.person;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.mapper.Array;
import net.hostsharing.hsadminng.rbac.grant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.role.RawRbacRoleRepository;
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.Arrays;
import java.util.List;
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealTestEntity.hsOfficePersonRealEntity;
import static net.hostsharing.hsadminng.rbac.grant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.role.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
@Import( { Context.class, JpaAttempt.class })
class HsOfficePersonRealRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
@Autowired
HsOfficePersonRealRepository personRealRepo;
@Autowired
RawRbacRoleRepository rawRoleRepo;
@Autowired
RawRbacGrantRepository rawGrantRepo;
@PersistenceContext
EntityManager em;
@Autowired
JpaAttempt jpaAttempt;
@MockBean
HttpServletRequest request;
@Autowired
private HsOfficePersonRealRepository hsOfficePersonRealRepository;
@Nested
class CreatePerson {
@Test
public void arbitraryUser_canCreateNewPerson() {
// given
context("selfregistered-user-drew@hostsharing.org");
final var count = personRealRepo.count();
// when
final var result = attempt(em, () -> toCleanup(personRealRepo.save(
hsOfficePersonRealEntity("another new person"))));
// then
result.assertSuccessful();
assertThat(result.returnedValue()).isNotNull().extracting(HsOfficePersonRealEntity::getUuid).isNotNull();
assertThatPersonIsPersisted(result.returnedValue());
assertThat(personRealRepo.count()).isEqualTo(count + 1);
}
@Test
public void createsAndGrantsRoles() {
// given
context("selfregistered-user-drew@hostsharing.org");
final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll());
// when
attempt(em, () -> toCleanup(
personRealRepo.save(hsOfficePersonRealEntity("another new person"))
)).assumeSuccessful();
// then
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(
Array.from(
initialRoleNames,
"hs_office.person#anothernewperson:OWNER",
"hs_office.person#anothernewperson:ADMIN",
"hs_office.person#anothernewperson:REFERRER"
));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(
Array.fromFormatted(
initialGrantNames,
"{ grant perm:hs_office.person#anothernewperson:INSERT>hs_office.relation to role:hs_office.person#anothernewperson:ADMIN by system and assume }",
"{ grant role:hs_office.person#anothernewperson:OWNER to user:selfregistered-user-drew@hostsharing.org by hs_office.person#anothernewperson:OWNER and assume }",
"{ grant role:hs_office.person#anothernewperson:OWNER to role:rbac.global#global:ADMIN by system and assume }",
"{ grant perm:hs_office.person#anothernewperson:UPDATE to role:hs_office.person#anothernewperson:ADMIN by system and assume }",
"{ grant perm:hs_office.person#anothernewperson:DELETE to role:hs_office.person#anothernewperson:OWNER by system and assume }",
"{ grant role:hs_office.person#anothernewperson:ADMIN to role:hs_office.person#anothernewperson:OWNER by system and assume }",
"{ grant perm:hs_office.person#anothernewperson:SELECT to role:hs_office.person#anothernewperson:REFERRER by system and assume }",
"{ grant role:hs_office.person#anothernewperson:REFERRER to role:hs_office.person#anothernewperson:ADMIN by system and assume }"
));
}
private void assertThatPersonIsPersisted(final HsOfficePersonRealEntity saved) {
final var found = personRealRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
}
}
@Nested
class FindAllPersons {
@Test
public void arbitraryUser_canViewAllPersons() {
// given
context("selfregistered-user-drew@hostsharing.org");
// when
final var result = personRealRepo.findPersonByOptionalNameLike(null);
// then
allThesePersonsAreReturned(
result,
"NP Smith, Peter",
"LP Second e.K.",
"IF Third OHG",
"UF Erben Bessler");
}
}
@Nested
class FindByCaptionLike {
@Test
public void arbitraryUser_canViewAllPersons() {
// given
context("selfregistered-user-drew@hostsharing.org");
// when
final var result = personRealRepo.findPersonByOptionalNameLike("Second");
// then
exactlyThesePersonsAreReturned(result, "Second e.K.");
}
}
@Test
public void auditJournalLogIsAvailable() {
// given
final var query = em.createNativeQuery("""
select currentTask, targetTable, targetOp, targetdelta->>'tradename', targetdelta->>'lastname'
from base.tx_journal_v
where targettable = 'hs_office.person';
""");
// when
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
// then
assertThat(customerLogEntries).map(Arrays::toString).contains(
"[creating person test-data, hs_office.person, INSERT, Hostsharing eG, null]",
"[creating person test-data, hs_office.person, INSERT, First GmbH, null]",
"[creating person test-data, hs_office.person, INSERT, Second e.K., null]",
"[creating person test-data, hs_office.person, INSERT, Third OHG, null]");
}
void exactlyThesePersonsAreReturned(final List<HsOfficePersonRealEntity> actualResult, final String... personCaptions) {
assertThat(actualResult)
.extracting(HsOfficePersonRealEntity::getTradeName)
.containsExactlyInAnyOrder(personCaptions);
}
void allThesePersonsAreReturned(final List<HsOfficePersonRealEntity> actualResult, final String... personCaptions) {
assertThat(actualResult)
.extracting(HsOfficePerson::toShortString)
.contains(personCaptions);
}
}

View File

@ -0,0 +1,14 @@
package net.hostsharing.hsadminng.hs.office.person;
public class HsOfficePersonRealTestEntity {
public static final HsOfficePersonRealEntity somePerson = hsOfficePersonRealEntity("some person");
public static HsOfficePersonRealEntity hsOfficePersonRealEntity(final String tradeName) {
return HsOfficePersonRealEntity.builder()
.personType(HsOfficePersonType.NATURAL_PERSON)
.tradeName(tradeName)
.build();
}
}