add hs-office-membership entity+repo + fix rbac
This commit is contained in:
parent
28bdd9220d
commit
e6f9484f99
@ -0,0 +1,88 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.office.membership;
|
||||||
|
|
||||||
|
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
|
||||||
|
import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType;
|
||||||
|
import com.vladmihalcea.hibernate.type.range.Range;
|
||||||
|
import lombok.*;
|
||||||
|
import net.hostsharing.hsadminng.Stringify;
|
||||||
|
import net.hostsharing.hsadminng.Stringifyable;
|
||||||
|
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||||
|
import org.hibernate.annotations.Fetch;
|
||||||
|
import org.hibernate.annotations.FetchMode;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
import org.hibernate.annotations.TypeDef;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.Stringify.stringify;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "hs_office_membership_rv")
|
||||||
|
@TypeDef(
|
||||||
|
name = "pgsql_enum",
|
||||||
|
typeClass = PostgreSQLEnumType.class
|
||||||
|
)
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@DisplayName("Membership")
|
||||||
|
@TypeDef(
|
||||||
|
typeClass = PostgreSQLRangeType.class,
|
||||||
|
defaultForType = Range.class
|
||||||
|
)
|
||||||
|
public class HsOfficeMembershipEntity implements Stringifyable {
|
||||||
|
|
||||||
|
private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class)
|
||||||
|
.withProp(HsOfficeMembershipEntity::getMemberNumber)
|
||||||
|
.withProp(e -> e.getPartner().toShortString())
|
||||||
|
.withProp(e -> e.getMainDebitor().toShortString())
|
||||||
|
.withProp(e -> e.getValidity().asString())
|
||||||
|
.withProp(HsOfficeMembershipEntity::getReasonForTermination)
|
||||||
|
.withSeparator(", ")
|
||||||
|
.quotedValues(false);
|
||||||
|
|
||||||
|
private @Id UUID uuid;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "partneruuid")
|
||||||
|
private HsOfficePartnerEntity partner;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@Fetch(FetchMode.JOIN)
|
||||||
|
@JoinColumn(name = "maindebitoruuid")
|
||||||
|
private HsOfficeDebitorEntity mainDebitor;
|
||||||
|
|
||||||
|
@Column(name = "membernumber")
|
||||||
|
private int memberNumber;
|
||||||
|
|
||||||
|
@Column(name = "validity", columnDefinition = "daterange")
|
||||||
|
private Range<LocalDate> validity;
|
||||||
|
|
||||||
|
@Column(name = "reasonfortermination")
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Type(type = "pgsql_enum")
|
||||||
|
private HsOfficeReasonForTermination reasonForTermination;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return stringify.apply(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toShortString() {
|
||||||
|
return String.valueOf(memberNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
void init() {
|
||||||
|
if (getReasonForTermination() == null) {
|
||||||
|
setReasonForTermination(HsOfficeReasonForTermination.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.office.membership;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembershipEntity, UUID> {
|
||||||
|
|
||||||
|
Optional<HsOfficeMembershipEntity> findByUuid(UUID id);
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT membership FROM HsOfficeMembershipEntity membership
|
||||||
|
WHERE :memberNumber is null
|
||||||
|
OR membership.memberNumber = :memberNumber
|
||||||
|
ORDER BY membership.memberNumber
|
||||||
|
""")
|
||||||
|
List<HsOfficeMembershipEntity> findMembershipByOptionalMemberNumber(Integer memberNumber);
|
||||||
|
|
||||||
|
List<HsOfficeMembershipEntity> findMembershipsByPartnerUuid(UUID partnerUuid);
|
||||||
|
|
||||||
|
HsOfficeMembershipEntity save(final HsOfficeMembershipEntity entity);
|
||||||
|
|
||||||
|
long count();
|
||||||
|
|
||||||
|
int deleteByUuid(UUID uuid);
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.office.membership;
|
||||||
|
|
||||||
|
public enum HsOfficeReasonForTermination {
|
||||||
|
NONE, CANCELLATION, TRANSFER, DEATH, LIQUIDATION, EXPULSION;
|
||||||
|
}
|
@ -15,7 +15,7 @@ create table if not exists hs_office_membership
|
|||||||
mainDebitorUuid uuid not null references hs_office_debitor(uuid),
|
mainDebitorUuid uuid not null references hs_office_debitor(uuid),
|
||||||
memberNumber numeric(5) not null,
|
memberNumber numeric(5) not null,
|
||||||
validity daterange not null,
|
validity daterange not null,
|
||||||
reasonForTermination HsOfficeReasonForTermination not null
|
reasonForTermination HsOfficeReasonForTermination not null default 'NONE'
|
||||||
);
|
);
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
@ -51,11 +51,13 @@ subgraph hsOfficeMembership
|
|||||||
role:hsOfficeDebitor.admin --> role:hsOfficeMembership.agent
|
role:hsOfficeDebitor.admin --> role:hsOfficeMembership.agent
|
||||||
%% outgoing
|
%% outgoing
|
||||||
role:hsOfficeMembership.agent --> role:hsOfficePartner.tenant
|
role:hsOfficeMembership.agent --> role:hsOfficePartner.tenant
|
||||||
role:hsOfficeMembership.admin --> role:hsOfficeDebitor.tenant
|
role:hsOfficeMembership.agent --> role:hsOfficeDebitor.tenant
|
||||||
|
|
||||||
role:hsOfficeMembership.tenant[membership.tenant]
|
role:hsOfficeMembership.tenant[membership.tenant]
|
||||||
%% incoming
|
%% incoming
|
||||||
role:hsOfficeMembership.agent --> role:hsOfficeMembership.tenant
|
role:hsOfficeMembership.agent --> role:hsOfficeMembership.tenant
|
||||||
|
role:hsOfficePartner.agent --> role:hsOfficeMembership.tenant
|
||||||
|
role:hsOfficeDebitor.agent --> role:hsOfficeMembership.tenant
|
||||||
%% outgoing
|
%% outgoing
|
||||||
role:hsOfficeMembership.tenant --> role:hsOfficePartner.guest
|
role:hsOfficeMembership.tenant --> role:hsOfficePartner.guest
|
||||||
role:hsOfficeMembership.tenant --> role:hsOfficeDebitor.guest
|
role:hsOfficeMembership.tenant --> role:hsOfficeDebitor.guest
|
||||||
@ -65,6 +67,8 @@ subgraph hsOfficeMembership
|
|||||||
role:hsOfficeMembership.guest --> perm:hsOfficeMembership.view{{membership.view}}
|
role:hsOfficeMembership.guest --> perm:hsOfficeMembership.view{{membership.view}}
|
||||||
%% incoming
|
%% incoming
|
||||||
role:hsOfficeMembership.tenant --> role:hsOfficeMembership.guest
|
role:hsOfficeMembership.tenant --> role:hsOfficeMembership.guest
|
||||||
|
role:hsOfficePartner.tenant --> role:hsOfficeMembership.guest
|
||||||
|
role:hsOfficeDebitor.tenant --> role:hsOfficeMembership.guest
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,26 +47,25 @@ begin
|
|||||||
perform createRoleWithGrants(
|
perform createRoleWithGrants(
|
||||||
hsOfficeMembershipAdmin(NEW),
|
hsOfficeMembershipAdmin(NEW),
|
||||||
permissions => array['edit'],
|
permissions => array['edit'],
|
||||||
incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)],
|
incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)]
|
||||||
outgoingSubRoles => array[hsOfficeDebitorTenant(newHsOfficeDebitor)]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
perform createRoleWithGrants(
|
perform createRoleWithGrants(
|
||||||
hsOfficeMembershipAgent(NEW),
|
hsOfficeMembershipAgent(NEW),
|
||||||
incomingSuperRoles => array[hsOfficeMembershipAdmin(NEW), hsOfficePartnerAdmin(newHsOfficePartner), hsOfficeDebitorAdmin(newHsOfficeDebitor)],
|
incomingSuperRoles => array[hsOfficeMembershipAdmin(NEW), hsOfficePartnerAdmin(newHsOfficePartner), hsOfficeDebitorAdmin(newHsOfficeDebitor)],
|
||||||
outgoingSubRoles => array[hsOfficePartnerTenant(newHsOfficePartner)]
|
outgoingSubRoles => array[hsOfficePartnerTenant(newHsOfficePartner), hsOfficeDebitorTenant(newHsOfficeDebitor)]
|
||||||
);
|
);
|
||||||
|
|
||||||
perform createRoleWithGrants(
|
perform createRoleWithGrants(
|
||||||
hsOfficeMembershipTenant(NEW),
|
hsOfficeMembershipTenant(NEW),
|
||||||
incomingSuperRoles => array[hsOfficeMembershipAgent(NEW)],
|
incomingSuperRoles => array[hsOfficeMembershipAgent(NEW), hsOfficePartnerAgent(newHsOfficePartner), hsOfficeDebitorAgent(newHsOfficeDebitor)],
|
||||||
outgoingSubRoles => array[hsOfficePartnerGuest(newHsOfficePartner), hsOfficeDebitorGuest(newHsOfficeDebitor)]
|
outgoingSubRoles => array[hsOfficePartnerGuest(newHsOfficePartner), hsOfficeDebitorGuest(newHsOfficeDebitor)]
|
||||||
);
|
);
|
||||||
|
|
||||||
perform createRoleWithGrants(
|
perform createRoleWithGrants(
|
||||||
hsOfficeMembershipGuest(NEW),
|
hsOfficeMembershipGuest(NEW),
|
||||||
permissions => array['view'],
|
permissions => array['view'],
|
||||||
incomingSuperRoles => array[hsOfficeMembershipTenant(NEW)]
|
incomingSuperRoles => array[hsOfficeMembershipTenant(NEW), hsOfficePartnerTenant(newHsOfficePartner), hsOfficeDebitorTenant(newHsOfficeDebitor)]
|
||||||
);
|
);
|
||||||
|
|
||||||
-- === END of code generated from Mermaid flowchart. ===
|
-- === END of code generated from Mermaid flowchart. ===
|
||||||
|
@ -18,7 +18,7 @@ declare
|
|||||||
newMemberNumber numeric;
|
newMemberNumber numeric;
|
||||||
begin
|
begin
|
||||||
idName := cleanIdentifier( forPartnerTradeName || '#' || forMainDebitorNumber);
|
idName := cleanIdentifier( forPartnerTradeName || '#' || forMainDebitorNumber);
|
||||||
currentTask := 'creating SEPA-mandate test-data ' || idName;
|
currentTask := 'creating Membership test-data ' || idName;
|
||||||
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
|
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
|
||||||
execute format('set local hsadminng.currentTask to %L', currentTask);
|
execute format('set local hsadminng.currentTask to %L', currentTask);
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ begin
|
|||||||
select d.* from hs_office_debitor d where d.debitorNumber = forMainDebitorNumber into relatedDebitor;
|
select d.* from hs_office_debitor d where d.debitorNumber = forMainDebitorNumber into relatedDebitor;
|
||||||
select coalesce(max(memberNumber)+1, 10001) from hs_office_membership into newMemberNumber;
|
select coalesce(max(memberNumber)+1, 10001) from hs_office_membership into newMemberNumber;
|
||||||
|
|
||||||
raise notice 'creating test SEPA-mandate: %', idName;
|
raise notice 'creating test Membership: %', idName;
|
||||||
raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner;
|
raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner;
|
||||||
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
|
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
|
||||||
insert
|
insert
|
||||||
|
@ -84,7 +84,14 @@ public class ArchitectureTest {
|
|||||||
public static final ArchRule HsOfficePartnerPackageRule = classes()
|
public static final ArchRule HsOfficePartnerPackageRule = classes()
|
||||||
.that().resideInAPackage("..hs.office.partner..")
|
.that().resideInAPackage("..hs.office.partner..")
|
||||||
.should().onlyBeAccessed().byClassesThat()
|
.should().onlyBeAccessed().byClassesThat()
|
||||||
.resideInAnyPackage("..hs.office.partner..", "..hs.office.debitor..");
|
.resideInAnyPackage("..hs.office.partner..", "..hs.office.debitor..", "..hs.office.membership..");
|
||||||
|
|
||||||
|
@ArchTest
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static final ArchRule HsOfficeMembershipPackageRule = classes()
|
||||||
|
.that().resideInAPackage("..hs.office.membership..")
|
||||||
|
.should().onlyBeAccessed().byClassesThat()
|
||||||
|
.resideInAnyPackage("..hs.office.membership..");
|
||||||
|
|
||||||
@ArchTest
|
@ArchTest
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.office.membership;
|
||||||
|
|
||||||
|
import com.vladmihalcea.hibernate.type.range.Range;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.persistence.PrePersist;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.testDebitor;
|
||||||
|
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.testPartner;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class HsOfficeMembershipEntityUnitTest {
|
||||||
|
|
||||||
|
final HsOfficeMembershipEntity givenMembership = HsOfficeMembershipEntity.builder()
|
||||||
|
.memberNumber(10001)
|
||||||
|
.partner(testPartner)
|
||||||
|
.mainDebitor(testDebitor)
|
||||||
|
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toStringContainsAllProps() {
|
||||||
|
final var result = givenMembership.toString();
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo("Membership(10001, Test Ltd., 10001, [2020-01-01,))");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toShortStringContainsMemberNumberOnly() {
|
||||||
|
final var result = givenMembership.toShortString();
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo("10001");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void initializesReasonForTerminationInPrePersistIfNull() throws Exception {
|
||||||
|
final var givenUninitializedMembership = new HsOfficeMembershipEntity();
|
||||||
|
assertThat(givenUninitializedMembership.getReasonForTermination()).as("precondition failed").isNull();
|
||||||
|
|
||||||
|
invokePrePersist(givenUninitializedMembership);
|
||||||
|
assertThat(givenUninitializedMembership.getReasonForTermination()).isEqualTo(HsOfficeReasonForTermination.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doesNotOverwriteReasonForTerminationInPrePersistIfNotNull() throws Exception {
|
||||||
|
givenMembership.setReasonForTermination(HsOfficeReasonForTermination.CANCELLATION);
|
||||||
|
|
||||||
|
invokePrePersist(givenMembership);
|
||||||
|
assertThat(givenMembership.getReasonForTermination()).isEqualTo(HsOfficeReasonForTermination.CANCELLATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void invokePrePersist(final HsOfficeMembershipEntity membershipEntity)
|
||||||
|
throws IllegalAccessException, InvocationTargetException {
|
||||||
|
final var prePersistMethod = Arrays.stream(HsOfficeMembershipEntity.class.getDeclaredMethods())
|
||||||
|
.filter(f -> f.getAnnotation(PrePersist.class) != null)
|
||||||
|
.findFirst();
|
||||||
|
assertThat(prePersistMethod).as("@PrePersist method not found").isPresent();
|
||||||
|
|
||||||
|
prePersistMethod.get().invoke(membershipEntity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,456 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.office.membership;
|
||||||
|
|
||||||
|
import com.vladmihalcea.hibernate.type.range.Range;
|
||||||
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
import net.hostsharing.hsadminng.context.ContextBasedTest;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
|
||||||
|
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
|
||||||
|
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
|
||||||
|
import net.hostsharing.test.Array;
|
||||||
|
import net.hostsharing.test.JpaAttempt;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
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.ComponentScan;
|
||||||
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantDisplaysOf;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
||||||
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||||
|
|
||||||
|
@DataJpaTest
|
||||||
|
@ComponentScan(basePackageClasses = { HsOfficeMembershipRepository.class, Context.class, JpaAttempt.class })
|
||||||
|
@DirtiesContext
|
||||||
|
class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
HsOfficeMembershipRepository membershipRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
HsOfficePartnerRepository partnerRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
HsOfficeDebitorRepository debitorRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RawRbacRoleRepository rawRoleRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RawRbacGrantRepository rawGrantRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
EntityManager em;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
JpaAttempt jpaAttempt;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
HttpServletRequest request;
|
||||||
|
|
||||||
|
Set<HsOfficeMembershipEntity> tempEntities = new HashSet<>();
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CreateMembership {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void globalAdmin_withoutAssumedRole_canCreateNewMembership() {
|
||||||
|
// given
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var count = membershipRepo.count();
|
||||||
|
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
|
||||||
|
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = attempt(em, () -> {
|
||||||
|
final var newMembership = toCleanup(HsOfficeMembershipEntity.builder()
|
||||||
|
.uuid(UUID.randomUUID())
|
||||||
|
.memberNumber(20001)
|
||||||
|
.partner(givenPartner)
|
||||||
|
.mainDebitor(givenDebitor)
|
||||||
|
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
|
||||||
|
.build());
|
||||||
|
return membershipRepo.save(newMembership);
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
result.assertSuccessful();
|
||||||
|
assertThat(result.returnedValue()).isNotNull().extracting(HsOfficeMembershipEntity::getUuid).isNotNull();
|
||||||
|
assertThatMembershipIsPersisted(result.returnedValue());
|
||||||
|
assertThat(membershipRepo.count()).isEqualTo(count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createsAndGrantsRoles() {
|
||||||
|
// given
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll());
|
||||||
|
final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()).stream()
|
||||||
|
.map(s -> s.replace("GmbH-firstcontact", ""))
|
||||||
|
.map(s -> s.replace("hs_office_", ""))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// when
|
||||||
|
attempt(em, () -> {
|
||||||
|
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
|
||||||
|
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0);
|
||||||
|
final var newMembership = toCleanup(HsOfficeMembershipEntity.builder()
|
||||||
|
.uuid(UUID.randomUUID())
|
||||||
|
.memberNumber(20002)
|
||||||
|
.partner(givenPartner)
|
||||||
|
.mainDebitor(givenDebitor)
|
||||||
|
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
|
||||||
|
.build());
|
||||||
|
return membershipRepo.save(newMembership);
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
final var all = rawRoleRepo.findAll();
|
||||||
|
assertThat(roleNamesOf(all)).containsExactlyInAnyOrder(Array.from(
|
||||||
|
initialRoleNames,
|
||||||
|
"hs_office_membership#20002FirstGmbH-firstcontact.admin",
|
||||||
|
"hs_office_membership#20002FirstGmbH-firstcontact.agent",
|
||||||
|
"hs_office_membership#20002FirstGmbH-firstcontact.guest",
|
||||||
|
"hs_office_membership#20002FirstGmbH-firstcontact.owner",
|
||||||
|
"hs_office_membership#20002FirstGmbH-firstcontact.tenant"));
|
||||||
|
assertThat(grantDisplaysOf(rawGrantRepo.findAll()))
|
||||||
|
.map(s -> s.replace("GmbH-firstcontact", ""))
|
||||||
|
.map(s -> s.replace("hs_office_", ""))
|
||||||
|
.containsExactlyInAnyOrder(Array.fromFormatted(
|
||||||
|
initialGrantNames,
|
||||||
|
|
||||||
|
// owner
|
||||||
|
"{ grant perm * on membership#20002First to role membership#20002First.owner by system and assume }",
|
||||||
|
"{ grant role membership#20002First.owner to role global#global.admin by system and assume }",
|
||||||
|
|
||||||
|
// admin
|
||||||
|
"{ grant perm edit on membership#20002First to role membership#20002First.admin by system and assume }",
|
||||||
|
"{ grant role membership#20002First.admin to role membership#20002First.owner by system and assume }",
|
||||||
|
|
||||||
|
// agent
|
||||||
|
"{ grant role membership#20002First.agent to role membership#20002First.admin by system and assume }",
|
||||||
|
"{ grant role partner#First.tenant to role membership#20002First.agent by system and assume }",
|
||||||
|
"{ grant role membership#20002First.agent to role debitor#10001First.admin by system and assume }",
|
||||||
|
"{ grant role membership#20002First.agent to role partner#First.admin by system and assume }",
|
||||||
|
"{ grant role debitor#10001First.tenant to role membership#20002First.agent by system and assume }",
|
||||||
|
|
||||||
|
// tenant
|
||||||
|
"{ grant role membership#20002First.tenant to role membership#20002First.agent by system and assume }",
|
||||||
|
"{ grant role partner#First.guest to role membership#20002First.tenant by system and assume }",
|
||||||
|
"{ grant role debitor#10001First.guest to role membership#20002First.tenant by system and assume }",
|
||||||
|
"{ grant role membership#20002First.tenant to role debitor#10001First.agent by system and assume }",
|
||||||
|
"{ grant role membership#20002First.tenant to role partner#First.agent by system and assume }",
|
||||||
|
|
||||||
|
// guest
|
||||||
|
"{ grant perm view on membership#20002First to role membership#20002First.guest by system and assume }",
|
||||||
|
"{ grant role membership#20002First.guest to role membership#20002First.tenant by system and assume }",
|
||||||
|
"{ grant role membership#20002First.guest to role partner#First.tenant by system and assume }",
|
||||||
|
"{ grant role membership#20002First.guest to role debitor#10001First.tenant by system and assume }",
|
||||||
|
|
||||||
|
null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertThatMembershipIsPersisted(final HsOfficeMembershipEntity saved) {
|
||||||
|
final var found = membershipRepo.findByUuid(saved.getUuid());
|
||||||
|
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindByPartnerUuidMemberships {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void globalAdmin_withoutAssumedRole_canViewAllMemberships() {
|
||||||
|
// given
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = membershipRepo.findMembershipsByPartnerUuid(givenPartner.getUuid());
|
||||||
|
|
||||||
|
// then
|
||||||
|
allTheseMembershipsAreReturned(result, "Membership(10001, First GmbH, 10001, [2022-10-01,), NONE)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void normalUser_canViewOnlyRelatedMemberships() {
|
||||||
|
// given:
|
||||||
|
context("person-FirstGmbH@example.com");
|
||||||
|
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
|
||||||
|
|
||||||
|
// when:
|
||||||
|
final var result = membershipRepo.findMembershipsByPartnerUuid(givenPartner.getUuid());
|
||||||
|
|
||||||
|
// then:
|
||||||
|
exactlyTheseMembershipsAreReturned(result, "Membership(10001, First GmbH, 10001, [2022-10-01,), NONE)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindByOptionalMemberNumber {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void globalAdmin_canViewArbitraryMembership() {
|
||||||
|
// given
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = membershipRepo.findMembershipByOptionalMemberNumber(10002);
|
||||||
|
|
||||||
|
// then
|
||||||
|
exactlyTheseMembershipsAreReturned(result, "Membership(10002, Second e.K., 10002, [2022-10-01,), NONE)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void debitorAdmin_canViewRelatedMemberships() {
|
||||||
|
// given
|
||||||
|
// context("person-FirstGmbH@example.com");
|
||||||
|
context("superuser-alex@hostsharing.net", "hs_office_partner#FirstGmbH-firstcontact.agent");
|
||||||
|
// context("superuser-alex@hostsharing.net", "hs_office_debitor#10001FirstGmbH-firstcontact.agent");
|
||||||
|
// context("superuser-alex@hostsharing.net", "hs_office_membership#10001FirstGmbH-firstcontact.admin");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = membershipRepo.findMembershipByOptionalMemberNumber(null);
|
||||||
|
|
||||||
|
// then
|
||||||
|
exactlyTheseMembershipsAreReturned(result, "Membership(10001, First GmbH, 10001, [2022-10-01,), NONE)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class UpdateMembership {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void globalAdmin_canUpdateValidityOfArbitraryMembership() {
|
||||||
|
// given
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var givenMembership = givenSomeTemporaryMembership("First", "First");
|
||||||
|
assertThatMembershipIsVisibleForUserWithRole(
|
||||||
|
givenMembership,
|
||||||
|
"hs_office_debitor#10001FirstGmbH-firstcontact.admin");
|
||||||
|
assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership);
|
||||||
|
final var newValidityEnd = LocalDate.now();
|
||||||
|
|
||||||
|
// when
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
givenMembership.setValidity(Range.closedOpen(
|
||||||
|
givenMembership.getValidity().lower(), newValidityEnd));
|
||||||
|
givenMembership.setReasonForTermination(HsOfficeReasonForTermination.CANCELLATION);
|
||||||
|
return toCleanup(membershipRepo.save(givenMembership));
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
result.assertSuccessful();
|
||||||
|
|
||||||
|
membershipRepo.deleteByUuid(givenMembership.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void debitorAdmin_canViewButNotUpdateRelatedMembership() {
|
||||||
|
// given
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var givenMembership = givenSomeTemporaryMembership("First", "First");
|
||||||
|
assertThatMembershipIsVisibleForUserWithRole(
|
||||||
|
givenMembership,
|
||||||
|
"hs_office_debitor#10001FirstGmbH-firstcontact.admin");
|
||||||
|
assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership);
|
||||||
|
final var newValidityEnd = LocalDate.now();
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net", "hs_office_debitor#10001FirstGmbH-firstcontact.admin");
|
||||||
|
givenMembership.setValidity(Range.closedOpen(
|
||||||
|
givenMembership.getValidity().lower(), newValidityEnd));
|
||||||
|
return membershipRepo.save(givenMembership);
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
|
||||||
|
"[403] Subject ", " is not allowed to update hs_office_membership uuid");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertThatMembershipExistsAndIsAccessibleToCurrentContext(final HsOfficeMembershipEntity saved) {
|
||||||
|
final var found = membershipRepo.findByUuid(saved.getUuid());
|
||||||
|
assertThat(found).isNotEmpty().get().isNotSameAs(saved).usingRecursiveComparison().isEqualTo(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertThatMembershipIsVisibleForUserWithRole(
|
||||||
|
final HsOfficeMembershipEntity entity,
|
||||||
|
final String assumedRoles) {
|
||||||
|
jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net", assumedRoles);
|
||||||
|
assertThatMembershipExistsAndIsAccessibleToCurrentContext(entity);
|
||||||
|
}).assertSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertThatMembershipIsNotVisibleForUserWithRole(
|
||||||
|
final HsOfficeMembershipEntity entity,
|
||||||
|
final String assumedRoles) {
|
||||||
|
jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net", assumedRoles);
|
||||||
|
final var found = membershipRepo.findByUuid(entity.getUuid());
|
||||||
|
assertThat(found).isEmpty();
|
||||||
|
}).assertSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class DeleteByUuid {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void globalAdmin_withoutAssumedRole_canDeleteAnyMembership() {
|
||||||
|
// given
|
||||||
|
context("superuser-alex@hostsharing.net", null);
|
||||||
|
final var givenMembership = givenSomeTemporaryMembership("First", "Second");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
membershipRepo.deleteByUuid(givenMembership.getUuid());
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
result.assertSuccessful();
|
||||||
|
assertThat(jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-fran@hostsharing.net", null);
|
||||||
|
return membershipRepo.findByUuid(givenMembership.getUuid());
|
||||||
|
}).assertSuccessful().returnedValue()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nonGlobalAdmin_canNotDeleteTheirRelatedMembership() {
|
||||||
|
// given
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var givenMembership = givenSomeTemporaryMembership("First", "Third");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net", "hs_office_debitor#10003ThirdOHG-thirdcontact.admin");
|
||||||
|
assumeThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent();
|
||||||
|
|
||||||
|
membershipRepo.deleteByUuid(givenMembership.getUuid());
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
result.assertExceptionWithRootCauseMessage(
|
||||||
|
JpaSystemException.class,
|
||||||
|
"[403] Subject ", " not allowed to delete hs_office_membership");
|
||||||
|
assertThat(jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
return membershipRepo.findByUuid(givenMembership.getUuid());
|
||||||
|
}).assertSuccessful().returnedValue()).isPresent(); // still there
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deletingAMembershipAlsoDeletesRelatedRolesAndGrants() {
|
||||||
|
// given
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll()));
|
||||||
|
final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll()));
|
||||||
|
final var givenMembership = givenSomeTemporaryMembership("First", "First");
|
||||||
|
assertThat(rawRoleRepo.findAll().size()).as("precondition failed: unexpected number of roles created")
|
||||||
|
.isEqualTo(initialRoleNames.length + 5);
|
||||||
|
assertThat(rawGrantRepo.findAll().size()).as("precondition failed: unexpected number of grants created")
|
||||||
|
.isEqualTo(initialGrantNames.length + 18);
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
return membershipRepo.deleteByUuid(givenMembership.getUuid());
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
result.assertSuccessful();
|
||||||
|
assertThat(result.returnedValue()).isEqualTo(1);
|
||||||
|
assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(initialRoleNames);
|
||||||
|
assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(initialGrantNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void auditJournalLogIsAvailable() {
|
||||||
|
// given
|
||||||
|
final var query = em.createNativeQuery("""
|
||||||
|
select c.currenttask, j.targettable, j.targetop
|
||||||
|
from tx_journal j
|
||||||
|
join tx_context c on j.contextId = c.contextId
|
||||||
|
where targettable = 'hs_office_membership';
|
||||||
|
""");
|
||||||
|
|
||||||
|
// when
|
||||||
|
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(customerLogEntries).map(Arrays::toString).contains(
|
||||||
|
"[creating Membership test-data FirstGmbH10001, hs_office_membership, INSERT]",
|
||||||
|
"[creating Membership test-data Seconde.K.10002, hs_office_membership, INSERT]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
@AfterEach
|
||||||
|
void cleanup() {
|
||||||
|
tempEntities.forEach(tempMembership -> {
|
||||||
|
jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net", null);
|
||||||
|
System.out.println("DELETING temporary membership: " + tempMembership.toString());
|
||||||
|
membershipRepo.deleteByUuid(tempMembership.getUuid());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net", null);
|
||||||
|
em.createQuery("DELETE FROM HsOfficeMembershipEntity WHERE memberNumber >= 20000");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private HsOfficeMembershipEntity givenSomeTemporaryMembership(final String partnerTradeName, final String debitorName) {
|
||||||
|
return jpaAttempt.transacted(() -> {
|
||||||
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerTradeName).get(0);
|
||||||
|
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).get(0);
|
||||||
|
final var newMembership = HsOfficeMembershipEntity.builder()
|
||||||
|
.uuid(UUID.randomUUID())
|
||||||
|
.memberNumber(20002)
|
||||||
|
.partner(givenPartner)
|
||||||
|
.mainDebitor(givenDebitor)
|
||||||
|
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
toCleanup(newMembership);
|
||||||
|
|
||||||
|
return membershipRepo.save(newMembership);
|
||||||
|
}).assertSuccessful().returnedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private HsOfficeMembershipEntity toCleanup(final HsOfficeMembershipEntity tempEntity) {
|
||||||
|
tempEntities.add(tempEntity);
|
||||||
|
return tempEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void exactlyTheseMembershipsAreReturned(
|
||||||
|
final List<HsOfficeMembershipEntity> actualResult,
|
||||||
|
final String... membershipNames) {
|
||||||
|
assertThat(actualResult)
|
||||||
|
.extracting(membershipEntity -> membershipEntity.toString())
|
||||||
|
.containsExactlyInAnyOrder(membershipNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
void allTheseMembershipsAreReturned(final List<HsOfficeMembershipEntity> actualResult, final String... membershipNames) {
|
||||||
|
assertThat(actualResult)
|
||||||
|
.extracting(membershipEntity -> membershipEntity.toString())
|
||||||
|
.contains(membershipNames);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user