diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java index 205a1e5b..724e10a4 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java @@ -144,6 +144,7 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable { with.permission(DELETE); }) .createSubRole(ADMIN, (with) -> { + with.incomingSuperRole("partnerRel", AGENT); with.permission(UPDATE); }) .createSubRole(REFERRER, (with) -> { diff --git a/src/main/resources/api-definition/hs-office/hs-office-membership-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-membership-schemas.yaml index 163f6f34..02fba043 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-membership-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-membership-schemas.yaml @@ -46,10 +46,6 @@ components: HsOfficeMembershipPatch: type: object properties: - mainDebitorUuid: - type: string - format: uuid - nullable: true validTo: type: string format: date @@ -69,10 +65,6 @@ components: type: string format: uuid nullable: false - mainDebitorUuid: - type: string - format: uuid - nullable: false memberNumberSuffix: type: string minLength: 2 @@ -95,7 +87,6 @@ components: required: - partnerUuid - memberNumberSuffix - - mainDebitorUuid - validFrom - membershipFeeBillable additionalProperties: false diff --git a/src/main/resources/db/changelog/303-hs-office-membership-rbac.md b/src/main/resources/db/changelog/303-hs-office-membership-rbac.md index e1e6b2cd..c6ccf003 100644 --- a/src/main/resources/db/changelog/303-hs-office-membership-rbac.md +++ b/src/main/resources/db/changelog/303-hs-office-membership-rbac.md @@ -1,6 +1,6 @@ ### rbac membership -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-18T16:31:23.329131137. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T17:09:08.826781619. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% @@ -146,6 +146,7 @@ role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer role:partnerRel:tenant -.-> role:partnerRel.contact:referrer role:partnerRel:admin ==> role:membership:owner role:membership:owner ==> role:membership:admin +role:partnerRel:agent ==> role:membership:admin role:membership:admin ==> role:membership:referrer role:membership:referrer ==> role:partnerRel:tenant diff --git a/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql b/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql index d8019c67..89ca543b 100644 --- a/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql +++ b/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-18T16:31:23.334581734. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-21T17:09:08.832004329. -- ============================================================================ @@ -53,7 +53,9 @@ begin perform createRoleWithGrants( hsOfficeMembershipAdmin(NEW), permissions => array['UPDATE'], - incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)] + incomingSuperRoles => array[ + hsOfficeMembershipOwner(NEW), + hsOfficeRelationshipAgent(newPartnerRel)] ); perform createRoleWithGrants( diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java index 63ea7306..bcd7e9ab 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java @@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.hs.office.membership; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository; -import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.mapper.Mapper; import org.junit.jupiter.api.BeforeEach; @@ -28,7 +27,6 @@ import java.util.UUID; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -76,7 +74,6 @@ public class HsOfficeMembershipControllerRestTest { .content(""" { "partnerUuid": null, - "mainDebitorUuid": "%s", "memberNumberSuffix": "01", "validFrom": "2022-10-13", "membershipFeeBillable": "true" @@ -91,40 +88,12 @@ public class HsOfficeMembershipControllerRestTest { .andExpect(jsonPath("message", is("[partnerUuid must not be null but is \"null\"]"))); } - @Test - void respondBadRequest_ifDebitorUuidIsMissing() throws Exception { - - // when - mockMvc.perform(MockMvcRequestBuilders - .post("/api/hs/office/memberships") - .header("current-user", "superuser-alex@hostsharing.net") - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "partnerUuid": "%s", - "mainDebitorUuid": null, - "memberNumberSuffix": "01", - "validFrom": "2022-10-13", - "membershipFeeBillable": "true" - } - """.formatted(UUID.randomUUID())) - .accept(MediaType.APPLICATION_JSON)) - - // then - .andExpect(status().is4xxClientError()) - .andExpect(jsonPath("statusCode", is(400))) - .andExpect(jsonPath("statusPhrase", is("Bad Request"))) - .andExpect(jsonPath("message", is("[mainDebitorUuid must not be null but is \"null\"]"))); - } - @Test void respondBadRequest_ifAnyGivenPartnerUuidCannotBeFound() throws Exception { // given final var givenPartnerUuid = UUID.randomUUID(); - final var givenMainDebitorUuid = UUID.randomUUID(); when(em.find(HsOfficePartnerEntity.class, givenPartnerUuid)).thenReturn(null); - when(em.find(HsOfficeDebitorEntity.class, givenMainDebitorUuid)).thenReturn(mock(HsOfficeDebitorEntity.class)); // when mockMvc.perform(MockMvcRequestBuilders @@ -134,12 +103,11 @@ public class HsOfficeMembershipControllerRestTest { .content(""" { "partnerUuid": "%s", - "mainDebitorUuid": "%s", "memberNumberSuffix": "01", "validFrom": "2022-10-13", "membershipFeeBillable": "true" } - """.formatted(givenPartnerUuid, givenMainDebitorUuid)) + """.formatted(givenPartnerUuid)) .accept(MediaType.APPLICATION_JSON)) // then @@ -149,38 +117,6 @@ public class HsOfficeMembershipControllerRestTest { .andExpect(jsonPath("message", is("Unable to find Partner with uuid " + givenPartnerUuid))); } - @Test - void respondBadRequest_ifAnyGivenDebitorUuidCannotBeFound() throws Exception { - - // given - final var givenPartnerUuid = UUID.randomUUID(); - final var givenMainDebitorUuid = UUID.randomUUID(); - when(em.find(HsOfficePartnerEntity.class, givenPartnerUuid)).thenReturn(mock(HsOfficePartnerEntity.class)); - when(em.find(HsOfficeDebitorEntity.class, givenMainDebitorUuid)).thenReturn(null); - - // when - mockMvc.perform(MockMvcRequestBuilders - .post("/api/hs/office/memberships") - .header("current-user", "superuser-alex@hostsharing.net") - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "partnerUuid": "%s", - "mainDebitorUuid": "%s", - "memberNumberSuffix": "01", - "validFrom": "2022-10-13", - "membershipFeeBillable": "true" - } - """.formatted(givenPartnerUuid, givenMainDebitorUuid)) - .accept(MediaType.APPLICATION_JSON)) - - // then - .andExpect(status().is4xxClientError()) - .andExpect(jsonPath("statusCode", is(400))) - .andExpect(jsonPath("statusPhrase", is("Bad Request"))) - .andExpect(jsonPath("message", is("Unable to find Debitor with uuid " + givenMainDebitorUuid))); - } - @ParameterizedTest @EnumSource(InvalidMemberSuffixVariants.class) void respondBadRequest_ifMemberNumberSuffixIsInvalid(final InvalidMemberSuffixVariants testCase) throws Exception { @@ -193,12 +129,11 @@ public class HsOfficeMembershipControllerRestTest { .content(""" { "partnerUuid": "%s", - "mainDebitorUuid": "%s", %s "validFrom": "2022-10-13", "membershipFeeBillable": "true" } - """.formatted(UUID.randomUUID(), UUID.randomUUID(), testCase.memberNumberSuffixEntry)) + """.formatted(UUID.randomUUID(), testCase.memberNumberSuffixEntry)) .accept(MediaType.APPLICATION_JSON)) // then diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityUnitTest.java index aeac829b..1c4d2dc6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityUnitTest.java @@ -25,7 +25,7 @@ class HsOfficeMembershipEntityUnitTest { @Test void toStringContainsAllProps() { final var result = givenMembership.toString(); - assertThat(result).isEqualTo("Membership(M-1000101, LP Test Ltd., D-1000100, [2020-01-01,))"); + assertThat(result).isEqualTo("Membership(M-1000101, P-10001, [2020-01-01,))"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java index 48bab952..e79ba5e5 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java @@ -25,7 +25,6 @@ import java.util.Arrays; import java.util.List; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; -import static net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.Include.ALL_NON_TEST_ENTITY_RELATED; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.test.JpaAttempt.attempt; import static org.assertj.core.api.Assertions.assertThat; @@ -130,6 +129,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl "{ grant role membership#M-1000117.owner to role relationship#HostsharingeG-with-PARTNER-FirstGmbH.admin by system and assume }", "{ grant role membership#M-1000117.owner to user superuser-alex@hostsharing.net by membership#M-1000117.owner and assume }", + // agent + "{ grant role membership#M-1000117.admin to role relationship#HostsharingeG-with-PARTNER-FirstGmbH.agent by system and assume }", + // referrer "{ grant perm SELECT on membership#M-1000117 to role membership#M-1000117.referrer by system and assume }", "{ grant role membership#M-1000117.referrer to role membership#M-1000117.admin by system and assume }", @@ -221,20 +223,20 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl } @Test - public void membershipAdmin_canViewButNotUpdateRelatedMembership() { + public void membershipReferrer_canViewButNotUpdateRelatedMembership() { // given context("superuser-alex@hostsharing.net"); final var givenMembership = givenSomeTemporaryMembership("First", "13"); assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership); assertThatMembershipIsVisibleForRole( givenMembership, - "s_office_membership#M-1000101.admin"); + "hs_office_membership#M-1000113.referrer"); final var newValidityEnd = LocalDate.now(); // when final var result = jpaAttempt.transacted(() -> { // TODO: we should test with debitor- and partner-admin as well - context("superuser-alex@hostsharing.net", "hs_office_membership#M-1000101.admin"); + context("superuser-alex@hostsharing.net", "hs_office_membership#M-1000113.referrer"); givenMembership.setValidity( Range.closedOpen(givenMembership.getValidity().lower(), newValidityEnd)); return membershipRepo.save(givenMembership); @@ -256,7 +258,6 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl final String assumedRoles) { jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net", assumedRoles); - generateRbacDiagramForCurrentSubjects(ALL_NON_TEST_ENTITY_RELATED); assertThatMembershipExistsAndIsAccessibleToCurrentContext(entity); }).assertSuccessful(); } @@ -286,14 +287,14 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl } @Test - public void debitorRelationAgent_canNotDeleteTheirRelatedMembership() { + public void partnerRelationAgent_canNotDeleteTheirRelatedMembership() { // given context("superuser-alex@hostsharing.net"); final var givenMembership = givenSomeTemporaryMembership("First", "14"); // when final var result = jpaAttempt.transacted(() -> { - context("superuser-alex@hostsharing.net", "hs_office_relationship#ThirdOHG-with-ACCOUNTING-ThirdOHG.agent"); + context("superuser-alex@hostsharing.net", "hs_office_relationship#HostsharingeG-with-PARTNER-FirstGmbH.agent"); assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent(); membershipRepo.deleteByUuid(givenMembership.getUuid());