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),
|
||||
memberNumber numeric(5) 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
|
||||
%% outgoing
|
||||
role:hsOfficeMembership.agent --> role:hsOfficePartner.tenant
|
||||
role:hsOfficeMembership.admin --> role:hsOfficeDebitor.tenant
|
||||
role:hsOfficeMembership.agent --> role:hsOfficeDebitor.tenant
|
||||
|
||||
role:hsOfficeMembership.tenant[membership.tenant]
|
||||
%% incoming
|
||||
role:hsOfficeMembership.agent --> role:hsOfficeMembership.tenant
|
||||
role:hsOfficePartner.agent --> role:hsOfficeMembership.tenant
|
||||
role:hsOfficeDebitor.agent --> role:hsOfficeMembership.tenant
|
||||
%% outgoing
|
||||
role:hsOfficeMembership.tenant --> role:hsOfficePartner.guest
|
||||
role:hsOfficeMembership.tenant --> role:hsOfficeDebitor.guest
|
||||
@ -65,6 +67,8 @@ subgraph hsOfficeMembership
|
||||
role:hsOfficeMembership.guest --> perm:hsOfficeMembership.view{{membership.view}}
|
||||
%% incoming
|
||||
role:hsOfficeMembership.tenant --> role:hsOfficeMembership.guest
|
||||
role:hsOfficePartner.tenant --> role:hsOfficeMembership.guest
|
||||
role:hsOfficeDebitor.tenant --> role:hsOfficeMembership.guest
|
||||
end
|
||||
|
||||
|
||||
|
@ -47,26 +47,25 @@ begin
|
||||
perform createRoleWithGrants(
|
||||
hsOfficeMembershipAdmin(NEW),
|
||||
permissions => array['edit'],
|
||||
incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)],
|
||||
outgoingSubRoles => array[hsOfficeDebitorTenant(newHsOfficeDebitor)]
|
||||
incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)]
|
||||
);
|
||||
|
||||
perform createRoleWithGrants(
|
||||
hsOfficeMembershipAgent(NEW),
|
||||
incomingSuperRoles => array[hsOfficeMembershipAdmin(NEW), hsOfficePartnerAdmin(newHsOfficePartner), hsOfficeDebitorAdmin(newHsOfficeDebitor)],
|
||||
outgoingSubRoles => array[hsOfficePartnerTenant(newHsOfficePartner)]
|
||||
outgoingSubRoles => array[hsOfficePartnerTenant(newHsOfficePartner), hsOfficeDebitorTenant(newHsOfficeDebitor)]
|
||||
);
|
||||
|
||||
perform createRoleWithGrants(
|
||||
hsOfficeMembershipTenant(NEW),
|
||||
incomingSuperRoles => array[hsOfficeMembershipAgent(NEW)],
|
||||
incomingSuperRoles => array[hsOfficeMembershipAgent(NEW), hsOfficePartnerAgent(newHsOfficePartner), hsOfficeDebitorAgent(newHsOfficeDebitor)],
|
||||
outgoingSubRoles => array[hsOfficePartnerGuest(newHsOfficePartner), hsOfficeDebitorGuest(newHsOfficeDebitor)]
|
||||
);
|
||||
|
||||
perform createRoleWithGrants(
|
||||
hsOfficeMembershipGuest(NEW),
|
||||
permissions => array['view'],
|
||||
incomingSuperRoles => array[hsOfficeMembershipTenant(NEW)]
|
||||
incomingSuperRoles => array[hsOfficeMembershipTenant(NEW), hsOfficePartnerTenant(newHsOfficePartner), hsOfficeDebitorTenant(newHsOfficeDebitor)]
|
||||
);
|
||||
|
||||
-- === END of code generated from Mermaid flowchart. ===
|
||||
|
@ -18,7 +18,7 @@ declare
|
||||
newMemberNumber numeric;
|
||||
begin
|
||||
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');
|
||||
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 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 debitor (%): %', relatedDebitor.uuid, relatedDebitor;
|
||||
insert
|
||||
|
@ -84,7 +84,14 @@ public class ArchitectureTest {
|
||||
public static final ArchRule HsOfficePartnerPackageRule = classes()
|
||||
.that().resideInAPackage("..hs.office.partner..")
|
||||
.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
|
||||
@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