From 6b6f8127bb4faf4e496dc2af8ef4cc9f1f9b9cdf Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 5 Oct 2022 17:22:33 +0200 Subject: [PATCH] add refundBankAccount to hs_office_debitor --- .../debitor/HsOfficeDebitorController.java | 14 ++-- .../office/debitor/HsOfficeDebitorEntity.java | 7 ++ .../hs-office/hs-office-debitor-schemas.yaml | 9 +++ .../238-hs-office-relationship-test-data.sql | 2 +- .../243-hs-office-bankaccount-rbac.md | 43 ++++++++++++ .../243-hs-office-bankaccount-rbac.sql | 14 +++- .../db/changelog/270-hs-office-debitor.sql | 16 ++--- .../changelog/273-hs-office-debitor-rbac.md | 35 +++++----- .../changelog/273-hs-office-debitor-rbac.sql | 55 ++++++++------- .../278-hs-office-debitor-test-data.sql | 16 +++-- ...ceBankAccountControllerAcceptanceTest.java | 6 ++ ...eBankAccountRepositoryIntegrationTest.java | 8 ++- ...OfficeContactControllerAcceptanceTest.java | 3 + ...OfficeDebitorControllerAcceptanceTest.java | 67 ++++++++++++++++--- ...fficeDebitorRepositoryIntegrationTest.java | 26 ++++--- src/test/java/net/hostsharing/test/Array.java | 5 +- 16 files changed, 231 insertions(+), 95 deletions(-) create mode 100644 src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.md diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java index b9c7fccb..44b70761 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java @@ -2,8 +2,8 @@ package net.hostsharing.hsadminng.hs.office.debitor; import net.hostsharing.hsadminng.Mapper; import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; -import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeDebitorsApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; @@ -35,12 +35,6 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { @Autowired private HsOfficeDebitorRepository debitorRepo; - @Autowired - private HsOfficePartnerRepository partnerRepo; - - @Autowired - private HsOfficeContactRepository contactRepo; - @Autowired private EntityManager em; @@ -141,10 +135,16 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { final BiConsumer DEBITOR_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { resource.setPartner(map(entity.getPartner(), HsOfficePartnerResource.class)); resource.setBillingContact(map(entity.getBillingContact(), HsOfficeContactResource.class)); + if ( entity.getRefundBankAccount() != null ) { + resource.setRefundBankAccount(map(entity.getRefundBankAccount(), HsOfficeBankAccountResource.class)); + } }; final BiConsumer DEBITOR_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { entity.setPartner(em.getReference(HsOfficePartnerEntity.class, resource.getPartnerUuid())); entity.setBillingContact(em.getReference(HsOfficeContactEntity.class, resource.getBillingContactUuid())); + if ( resource.getRefundBankAccountUuid() != null ) { + entity.setRefundBankAccount(em.getReference(HsOfficeBankAccountEntity.class, resource.getRefundBankAccountUuid())); + } }; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index dd7c39ee..6c0cbe5f 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -4,6 +4,7 @@ import lombok.*; import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.Stringify; import net.hostsharing.hsadminng.Stringifyable; +import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; @@ -45,6 +46,12 @@ public class HsOfficeDebitorEntity implements Stringifyable { private @Column(name = "vatcountrycode") String vatCountryCode; private @Column(name = "vatbusiness") boolean vatBusiness; + + @ManyToOne + @JoinColumn(name = "refundbankaccountuuid") + private HsOfficeBankAccountEntity refundBankAccount; + + @Override public String toString() { return stringify.apply(this); diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitor-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitor-schemas.yaml index 88c291a8..56b1a941 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-debitor-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-debitor-schemas.yaml @@ -25,6 +25,8 @@ components: pattern: '^[A_Z][A-Z]$' vatBusiness: type: boolean + refundBankAccount: + $ref: './hs-office-bankaccount-schemas.yaml#/components/schemas/HsOfficeBankAccount' HsOfficeDebitorPatch: type: object @@ -43,6 +45,10 @@ components: vatBusiness: type: boolean nullable: true + refundBankAccountUuid: + type: string + format: uuid + nullable: true HsOfficeDebitorInsert: type: object @@ -65,6 +71,9 @@ components: pattern: '^[A_Z][A-Z]$' vatBusiness: type: boolean + refundBankAccountUuid: + type: string + format: uuid required: - partnerUuid - billingContactUuid diff --git a/src/main/resources/db/changelog/238-hs-office-relationship-test-data.sql b/src/main/resources/db/changelog/238-hs-office-relationship-test-data.sql index 64e7be12..628c377a 100644 --- a/src/main/resources/db/changelog/238-hs-office-relationship-test-data.sql +++ b/src/main/resources/db/changelog/238-hs-office-relationship-test-data.sql @@ -22,7 +22,7 @@ declare contact hs_office_contact; begin - idName := cleanIdentifier( anchorPersonTradeName|| '-' || holderPersonFamilyName); + idName := cleanIdentifier( anchorPersonTradeName || '-' || holderPersonFamilyName); currentTask := 'creating RBAC test relationship ' || idName; call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin'); execute format('set local hsadminng.currentTask to %L', currentTask); diff --git a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.md b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.md new file mode 100644 index 00000000..07d60244 --- /dev/null +++ b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.md @@ -0,0 +1,43 @@ +### hs_office_bankaccount RBAC Roles + +```mermaid +flowchart TB + +%% ---------- generated start: ---------- + +subgraph global + role:global.admin[global.admin] +end + +subgraph context + user:current([current]) +end + +subgraph bankaccount + + subgraph roles[ ] + role:bankaccount.owner[[bankaccount.owner]] + role:bankaccount.admin[[bankaccount.admin]] + role:bankaccount.tenant[[bankaccount.tenant]] + end + + subgraph perms[ ] + perm:bankaccount.delete{{bankaccount.delete}} + perm:bankaccount.view{{bankaccount.view}} + end + +end + +%% ---------- generated end. ---------- + +role:bankaccount.owner --> perm:bankaccount.delete + +role:global.admin --> role:bankaccount.owner +user:current --> role:bankaccount.owner + +role:bankaccount.owner --> role:bankaccount.admin + +role:bankaccount.admin --> role:bankaccount.tenant +role:bankaccount.tenant --> perm:bankaccount.view +``` + diff --git a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql index baa63112..3d247e06 100644 --- a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql +++ b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql @@ -28,6 +28,7 @@ create or replace function createRbacRolesForHsOfficeBankAccount() strict as $$ declare ownerRole uuid; + adminRole uuid; begin if TG_OP <> 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT'; @@ -43,15 +44,22 @@ begin grantedByRole(globalAdmin()) ); + -- the admin role for those related users who can view the data and related records + adminRole := createRole( + hsOfficeBankAccountAdmin(NEW), + -- Where bankaccounts can be created, assigned, re-assigned and deleted, they cannot be updated. + -- Thus SQL UPDATE and 'edit' permission are being implemented. + withoutPermissions(), + beneathRole(ownerRole) + ); + -- TODO.spec: assumption can not be updated - -- Where bankaccounts can be created, assigned, re-assigned and deleted, they cannot be updated. - -- Thus SQL UPDATE and 'edit' permission are being implemented. -- the tenant role for those related users who can view the data perform createRole( hsOfficeBankAccountTenant(NEW), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRole(ownerRole) + beneathRole(adminRole) ); return NEW; diff --git a/src/main/resources/db/changelog/270-hs-office-debitor.sql b/src/main/resources/db/changelog/270-hs-office-debitor.sql index 30808f02..6831a301 100644 --- a/src/main/resources/db/changelog/270-hs-office-debitor.sql +++ b/src/main/resources/db/changelog/270-hs-office-debitor.sql @@ -6,14 +6,14 @@ create table hs_office_debitor ( - uuid uuid unique references RbacObject (uuid) initially deferred, - partnerUuid uuid not null references hs_office_partner(uuid), - debitorNumber numeric(5) not null, - billingContactUuid uuid not null references hs_office_contact(uuid), - vatId varchar(24), -- TODO.spec: here or in person? - vatCountryCode varchar(2), - vatBusiness boolean not null, -- TODO.spec: more of such? - bankAccountUuid uuid references hs_office_bankaccount(uuid) + uuid uuid unique references RbacObject (uuid) initially deferred, + partnerUuid uuid not null references hs_office_partner(uuid), + debitorNumber numeric(5) not null, + billingContactUuid uuid not null references hs_office_contact(uuid), + vatId varchar(24), -- TODO.spec: here or in person? + vatCountryCode varchar(2), + vatBusiness boolean not null, -- TODO.spec: more of such? + refundBankAccountUuid uuid references hs_office_bankaccount(uuid) -- TODO.impl: SEPA-mandate ); --// diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.md b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.md index d89030b2..7d8d0ed0 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.md +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.md @@ -4,7 +4,6 @@ flowchart TB; subgraph bankaccount; -direction TB; %% oversimplified version for now %% @@ -13,36 +12,36 @@ direction TB; %% e.g. package admins could see the debitors bank account, %% except if we do NOT use the debitor in the hosting super module. - %% role:bankaccount.owner - role:bankaccount.owner --> perm:bankaccount.*; + role:bankaccount.tenant --> perm:bankaccount.view{{bankaccount.view}}; end; subgraph debitor[" "]; direction TB; - %% role:debitor.owner - role:debitor.owner --> perm:debitor.*; - role:debitor.owner --> role:bankaccount.owner; + role:debitor.owner[[debitor.owner]] + role:debitor.owner --> perm:debitor.*{{debitor.*}}; - %% role:debitor.admin - role:debitor.admin --> perm:debitor.edit; - role:debitor.owner --> role:debitor.admin; + role:debitor.admin[[debitor.admin]] + %% super-roles + role:debitor.owner --> role:debitor.admin; + role:partner.admin --> role:debitor.admin; + role:person.admin --> role:debitor.admin; + role:contact.admin --> role:debitor.admin; + %% sub-roles + role:debitor.admin --> role:partner.tenant; + role:debitor.admin --> role:person.tenant; + role:debitor.admin --> role:contact.tenant; + role:debitor.admin --> role:bankaccount.tenant; - %% role:debitor.tenant - role:debitor.tenant --> perm:debitor.view; + role:debitor.tenant[[debitor.tenant]] + role:debitor.tenant --> perm:debitor.view{{debitor.view}}; %% super-roles role:debitor.admin --> role:debitor.tenant; - role:partner.admin --> role:debitor.tenant; - role:person.admin --> role:debitor.tenant; - role:contact.admin --> role:debitor.tenant; %% sub-roles - role:debitor.tenant --> role:partner.tenant; - role:debitor.tenant --> role:person.tenant; - role:debitor.tenant --> role:contact.tenant; + end; subgraph global; -direction TB; role:global.admin --> role:debitor.owner; end; diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql index 356fe094..dbe897d7 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql @@ -35,6 +35,7 @@ declare newPerson hs_office_person; oldContact hs_office_contact; newContact hs_office_contact; + newBankAccount hs_office_bankaccount; begin hsOfficeDebitorTenant := hsOfficeDebitorTenant(NEW); @@ -42,7 +43,7 @@ begin select * from hs_office_partner as p where p.uuid = NEW.partnerUuid into newPartner; select * from hs_office_person as p where p.uuid = newPartner.personUuid into newPerson; select * from hs_office_contact as c where c.uuid = NEW.billingContactUuid into newContact; - + select * from hs_office_bankaccount as b where b.uuid = NEW.refundBankAccountUuid into newBankAccount; if TG_OP = 'INSERT' then -- the owner role with full access for the global admins @@ -55,23 +56,25 @@ begin -- the admin role with full access for owner adminRole = createRole( hsOfficeDebitorAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), - beneathRole(ownerRole) + withoutPermissions(), + beneathRoles(array [ + hsOfficeDebitorOwner(NEW), + hsOfficePartnerAdmin(newPartner), + hsOfficePersonAdmin(newPerson), + hsOfficeContactAdmin(newContact), + hsOfficeBankAccountAdmin(newBankAccount)]), + withSubRoles(array [ + hsOfficePartnerTenant(newPartner), + hsOfficePersonTenant(newPerson), + hsOfficeContactTenant(newContact), + hsOfficeBankAccountTenant(newBankAccount)]) ); -- the tenant role for those related users who can view the data perform createRole( hsOfficeDebitorTenant, grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRoles(array[ - hsOfficeDebitorAdmin(NEW), - hsOfficePartnerAdmin(newPartner), - hsOfficePersonAdmin(newPerson), - hsOfficeContactAdmin(newContact)]), - withSubRoles(array[ - hsOfficePartnerTenant(newPartner), - hsOfficePersonTenant(newPerson), - hsOfficeContactTenant(newContact)]) + beneathRole(hsOfficeDebitorAdmin(NEW)) ); elsif TG_OP = 'UPDATE' then @@ -79,21 +82,23 @@ begin if OLD.partnerUuid <> NEW.partnerUuid then select * from hs_office_partner as p where p.uuid = OLD.partnerUuid into oldPartner; - call revokeRoleFromRole( hsOfficeDebitorTenant, hsOfficePartnerAdmin(oldPartner) ); - call grantRoleToRole( hsOfficeDebitorTenant, hsOfficePartnerAdmin(newPartner) ); + call revokeRoleFromRole(hsOfficeDebitorAdmin(OLD), hsOfficePartnerAdmin(oldPartner)); + call grantRoleToRole(hsOfficeDebitorAdmin(NEW), hsOfficePartnerAdmin(newPartner)); - call revokeRoleFromRole( hsOfficePartnerTenant(oldPartner), hsOfficeDebitorTenant ); - call grantRoleToRole( hsOfficePartnerTenant(newPartner), hsOfficeDebitorTenant ); + call revokeRoleFromRole(hsOfficePartnerTenant(oldPartner), hsOfficeDebitorAdmin(OLD)); + call grantRoleToRole(hsOfficePartnerTenant(newPartner), hsOfficeDebitorAdmin(NEW)); + + -- TODO: What about the person of the partner? And what if the person of the partner changes? end if; if OLD.billingContactUuid <> NEW.billingContactUuid then select * from hs_office_contact as c where c.uuid = OLD.billingContactUuid into oldContact; - call revokeRoleFromRole( hsOfficeDebitorTenant, hsOfficeContactAdmin(oldContact) ); - call grantRoleToRole( hsOfficeDebitorTenant, hsOfficeContactAdmin(newContact) ); + call revokeRoleFromRole(hsOfficeDebitorAdmin(OLD), hsOfficeContactAdmin(oldContact)); + call grantRoleToRole(hsOfficeDebitorAdmin(NEW), hsOfficeContactAdmin(newContact)); - call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeDebitorTenant ); - call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeDebitorTenant ); + call revokeRoleFromRole(hsOfficeContactTenant(oldContact), hsOfficeDebitorAdmin(OLD)); + call grantRoleToRole(hsOfficeContactTenant(newContact), hsOfficeDebitorAdmin(NEW)); end if; else raise exception 'invalid usage of TRIGGER'; @@ -136,8 +141,8 @@ call generateRbacIdentityView('hs_office_debitor', $idName$ --changeset hs-office-debitor-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('hs_office_debitor', - 'target.debitorNumber', - $updates$ + 'target.debitorNumber', + $updates$ billingContactUuid = new.billingContactUuid, vatId = new.vatId, vatCountryCode = new.vatCountryCode, @@ -153,9 +158,9 @@ call generateRbacRestrictedView('hs_office_debitor', */ do language plpgsql $$ declare - addDebitorPermissions uuid[]; - globalObjectUuid uuid; - globalAdminRoleUuid uuid ; + addDebitorPermissions uuid[]; + globalObjectUuid uuid; + globalAdminRoleUuid uuid ; begin call defineContext('granting global new-debitor permission to global admin role', null, null, null); diff --git a/src/main/resources/db/changelog/278-hs-office-debitor-test-data.sql b/src/main/resources/db/changelog/278-hs-office-debitor-test-data.sql index 98fa4478..208f6823 100644 --- a/src/main/resources/db/changelog/278-hs-office-debitor-test-data.sql +++ b/src/main/resources/db/changelog/278-hs-office-debitor-test-data.sql @@ -11,11 +11,12 @@ create or replace procedure createHsOfficeDebitorTestData( partnerTradeName varchar, billingContactLabel varchar ) language plpgsql as $$ declare - currentTask varchar; - idName varchar; - relatedPartner hs_office_partner; - relatedContact hs_office_contact; - newDebitorNumber numeric(6); + currentTask varchar; + idName varchar; + relatedPartner hs_office_partner; + relatedContact hs_office_contact; + relatedBankAccountUuid uuid; + newDebitorNumber numeric(6); begin idName := cleanIdentifier( partnerTradeName|| '-' || billingContactLabel); currentTask := 'creating RBAC test debitor ' || idName; @@ -26,14 +27,15 @@ begin join hs_office_person person on person.uuid = partner.personUuid where person.tradeName = partnerTradeName into relatedPartner; select c.* from hs_office_contact c where c.label = billingContactLabel into relatedContact; + select b.uuid from hs_office_bankaccount b where b.holder = partnerTradeName into relatedBankAccountUuid; select coalesce(max(debitorNumber)+1, 10001) from hs_office_debitor into newDebitorNumber; raise notice 'creating test debitor: % (#%)', idName, newDebitorNumber; raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner; raise notice '- using billingContact (%): %', relatedContact.uuid, relatedContact; insert - into hs_office_debitor (uuid, partneruuid, debitornumber, billingcontactuuid, vatbusiness) - values (uuid_generate_v4(), relatedPartner.uuid, newDebitorNumber, relatedContact.uuid, true); + into hs_office_debitor (uuid, partneruuid, debitornumber, billingcontactuuid, vatbusiness, refundbankaccountuuid) + values (uuid_generate_v4(), relatedPartner.uuid, newDebitorNumber, relatedContact.uuid, true, relatedBankAccountUuid); end; $$; --// diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerAcceptanceTest.java index bb6b1f37..91024f0e 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerAcceptanceTest.java @@ -343,6 +343,12 @@ class HsOfficeBankAccountControllerAcceptanceTest { @BeforeEach @AfterEach void cleanup() { + jpaAttempt.transacted(() -> { + context.define("superuser-alex@hostsharing.net", null); + tempBankAccountUuids.addAll( + bankAccountRepo.findByOptionalHolderLike("some temp acc").stream().map(HsOfficeBankAccountEntity::getUuid).toList() + ); + }); tempBankAccountUuids.forEach(uuid -> { jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net", null); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountRepositoryIntegrationTest.java index 890d218a..52de04ed 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountRepositoryIntegrationTest.java @@ -109,13 +109,15 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTest { assertThat(roleNamesOf(roles)).containsExactlyInAnyOrder(Array.from( initialRoleNames, "hs_office_bankaccount#sometempaccC.owner", + "hs_office_bankaccount#sometempaccC.admin", "hs_office_bankaccount#sometempaccC.tenant" )); assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.from( initialGrantNames, "{ grant role hs_office_bankaccount#sometempaccC.owner to role global#global.admin by system and assume }", "{ grant perm delete on hs_office_bankaccount#sometempaccC to role hs_office_bankaccount#sometempaccC.owner by system and assume }", - "{ grant role hs_office_bankaccount#sometempaccC.tenant to role hs_office_bankaccount#sometempaccC.owner by system and assume }", + "{ grant role hs_office_bankaccount#sometempaccC.tenant to role hs_office_bankaccount#sometempaccC.admin by system and assume }", + "{ grant role hs_office_bankaccount#sometempaccC.admin to role hs_office_bankaccount#sometempaccC.owner by system and assume }", "{ grant perm view on hs_office_bankaccount#sometempaccC to role hs_office_bankaccount#sometempaccC.tenant by system and assume }", "{ grant role hs_office_bankaccount#sometempaccC.owner to user selfregistered-user-drew@hostsharing.org by global#global.admin and assume }" )); @@ -253,9 +255,9 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTest { final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()); final var givenBankAccount = givenSomeTemporaryBankAccount("selfregistered-user-drew@hostsharing.org"); assertThat(rawRoleRepo.findAll().size()).as("unexpected number of roles created") - .isEqualTo(initialRoleNames.size() + 2); + .isEqualTo(initialRoleNames.size() + 3); assertThat(rawGrantRepo.findAll().size()).as("unexpected number of grants created") - .isEqualTo(initialGrantNames.size() + 5); + .isEqualTo(initialGrantNames.size() + 6); // when final var result = jpaAttempt.transacted(() -> { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java index b018b9fb..bfef17ee 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java @@ -8,7 +8,9 @@ import net.hostsharing.hsadminng.context.Context; import net.hostsharing.test.JpaAttempt; import org.apache.commons.lang3.RandomStringUtils; import org.json.JSONException; +import org.junit.Before; 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; @@ -363,6 +365,7 @@ class HsOfficeContactControllerAcceptanceTest { return tempContactUuid; } + @BeforeEach @AfterEach void cleanup() { tempContactUuids.forEach(uuid -> { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java index e699de81..e6dde210 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java @@ -5,6 +5,7 @@ import io.restassured.http.ContentType; import net.hostsharing.hsadminng.Accepts; import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository; import net.hostsharing.test.JpaAttempt; @@ -55,6 +56,9 @@ class HsOfficeDebitorControllerAcceptanceTest { @Autowired HsOfficeContactRepository contactRepo; + @Autowired + HsOfficeBankAccountRepository bankAccountRepo; + @Autowired JpaAttempt jpaAttempt; @@ -84,7 +88,8 @@ class HsOfficeDebitorControllerAcceptanceTest { "billingContact": { "label": "first contact" }, "vatId": null, "vatCountryCode": null, - "vatBusiness": true + "vatBusiness": true, + "refundBankAccount": { "holder": "First GmbH" } }, { "debitorNumber": 10002, @@ -92,7 +97,8 @@ class HsOfficeDebitorControllerAcceptanceTest { "billingContact": { "label": "second contact" }, "vatId": null, "vatCountryCode": null, - "vatBusiness": true + "vatBusiness": true, + "refundBankAccount": { "holder": "Second e.K." } }, { "debitorNumber": 10003, @@ -100,7 +106,8 @@ class HsOfficeDebitorControllerAcceptanceTest { "billingContact": { "label": "third contact" }, "vatId": null, "vatCountryCode": null, - "vatBusiness": true + "vatBusiness": true, + "refundBankAccount": { "holder": "Third OHG" } } ] """)); @@ -137,14 +144,15 @@ class HsOfficeDebitorControllerAcceptanceTest { @Nested @Accepts({ "Debitor:C(Create)" }) - class AddDebitor { + class CreateDebitor { @Test - void globalAdmin_withoutAssumedRole_canAddDebitor() { + void globalAdmin_withoutAssumedRole_canAddDebitorWithBankAccount() { context.define("superuser-alex@hostsharing.net"); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Third").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("forth").get(0); + final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Fourth").get(0); final var location = RestAssured // @formatter:off .given() @@ -157,9 +165,10 @@ class HsOfficeDebitorControllerAcceptanceTest { "debitorNumber": "%s", "vatId": "VAT123456", "vatCountryCode": "DE", - "vatBusiness": true + "vatBusiness": true, + "refundBankAccountUuid": "%s" } - """.formatted( givenPartner.getUuid(), givenContact.getUuid(), nextDebitorNumber++)) + """.formatted( givenPartner.getUuid(), givenContact.getUuid(), nextDebitorNumber++, givenBankAccount.getUuid())) .port(port) .when() .post("http://localhost/api/hs/office/debitors") @@ -170,6 +179,7 @@ class HsOfficeDebitorControllerAcceptanceTest { .body("vatId", is("VAT123456")) .body("billingContact.label", is(givenContact.getLabel())) .body("partner.person.tradeName", is(givenPartner.getPerson().getTradeName())) + .body("refundBankAccount.holder", is(givenBankAccount.getHolder())) .header("Location", startsWith("http://localhost")) .extract().header("Location"); // @formatter:on @@ -179,6 +189,46 @@ class HsOfficeDebitorControllerAcceptanceTest { assertThat(newUserUuid).isNotNull(); } + @Test + void globalAdmin_withoutAssumedRole_canAddDebitorWithoutBankAccount() { + + context.define("superuser-alex@hostsharing.net"); + final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Third").get(0); + final var givenContact = contactRepo.findContactByOptionalLabelLike("forth").get(0); + + final var location = RestAssured // @formatter:off + .given() + .header("current-user", "superuser-alex@hostsharing.net") + .contentType(ContentType.JSON) + .body(""" + { + "partnerUuid": "%s", + "billingContactUuid": "%s", + "debitorNumber": "%s", + "vatId": "VAT123456", + "vatCountryCode": "DE", + "vatBusiness": true + } + """.formatted( givenPartner.getUuid(), givenContact.getUuid(), nextDebitorNumber++)) + .port(port) + .when() + .post("http://localhost/api/hs/office/debitors") + .then().log().all().assertThat() + .statusCode(201) + .contentType(ContentType.JSON) + .body("uuid", isUuidValid()) + .body("vatId", is("VAT123456")) + .body("billingContact.label", is(givenContact.getLabel())) + .body("partner.person.tradeName", is(givenPartner.getPerson().getTradeName())) + .header("Location", startsWith("http://localhost")) + .extract().header("Location"); // @formatter:on + + // finally, the new debitor can be accessed under the generated UUID + final var newUserUuid = toCleanup(UUID.fromString( + location.substring(location.lastIndexOf('/') + 1))); + assertThat(newUserUuid).isNotNull(); + } + @Test void globalAdmin_canNotAddDebitor_ifContactDoesNotExist() { @@ -300,7 +350,8 @@ class HsOfficeDebitorControllerAcceptanceTest { .body("", lenientlyEquals(""" { "partner": { person: { "tradeName": "First GmbH" } }, - "billingContact": { "label": "first contact" } + "billingContact": { "label": "first contact" }, + "refundBankAccount": { "holder": "First GmbH" } } """)); // @formatter:on } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index 2e78f7d0..b478bcb0 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -122,21 +122,19 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { "hs_office_debitor#20002Fourthe.G.-forthcontact.tenant")); assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromSkippingNull( initialGrantNames, + "{ grant perm * on hs_office_debitor#20002Fourthe.G.-forthcontact to role hs_office_debitor#20002Fourthe.G.-forthcontact.owner by system and assume }", + "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.owner to role global#global.admin by system and assume }", - "{ grant perm * on hs_office_debitor#20002Fourthe.G.-forthcontact to role hs_office_debitor#20002Fourthe.G.-forthcontact.owner by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.owner to role global#global.admin by system and assume }", + "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.admin to role hs_office_debitor#20002Fourthe.G.-forthcontact.owner by system and assume }", + "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.admin to role hs_office_partner#Fourthe.G.-forthcontact.admin by system and assume }", + "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.admin to role hs_office_person#Fourthe.G..admin by system and assume }", + "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.admin to role hs_office_contact#forthcontact.admin by system and assume }", + "{ grant role hs_office_contact#forthcontact.tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", + "{ grant role hs_office_partner#Fourthe.G.-forthcontact.tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", + "{ grant role hs_office_person#Fourthe.G..tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", + "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", - "{ grant perm edit on hs_office_debitor#20002Fourthe.G.-forthcontact to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.admin to role hs_office_debitor#20002Fourthe.G.-forthcontact.owner by system and assume }", - - "{ grant perm view on hs_office_debitor#20002Fourthe.G.-forthcontact to role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant to role hs_office_contact#forthcontact.admin by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant to role hs_office_partner#Fourthe.G.-forthcontact.admin by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant to role hs_office_person#Fourthe.G..admin by system and assume }", - "{ grant role hs_office_partner#Fourthe.G.-forthcontact.tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant by system and assume }", - "{ grant role hs_office_contact#forthcontact.tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant by system and assume }", - "{ grant role hs_office_person#Fourthe.G..tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant by system and assume }", + "{ grant perm view on hs_office_debitor#20002Fourthe.G.-forthcontact to role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant by system and assume }", null)); } @@ -394,7 +392,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { assertThat(rawRoleRepo.findAll().size()).as("precondition failed: unexpected number of roles created") .isEqualTo(initialRoleNames.length + 3); assertThat(rawGrantRepo.findAll().size()).as("precondition failed: unexpected number of grants created") - .isEqualTo(initialGrantNames.length + 12); + .isEqualTo(initialGrantNames.length + 11); // when final var result = jpaAttempt.transacted(() -> { diff --git a/src/test/java/net/hostsharing/test/Array.java b/src/test/java/net/hostsharing/test/Array.java index 321efafe..5eb40887 100644 --- a/src/test/java/net/hostsharing/test/Array.java +++ b/src/test/java/net/hostsharing/test/Array.java @@ -24,7 +24,10 @@ public class Array { public static String[] fromSkippingNull(final List initialList, final String... additionalStrings) { final var resultList = new ArrayList<>(initialList); - resultList.addAll(Arrays.stream(additionalStrings).filter(Objects::nonNull).toList()); + resultList.addAll(Arrays.stream(additionalStrings) + .filter(Objects::nonNull) + .map(s -> s.replaceAll(" *", " ")) + .toList()); return resultList.toArray(String[]::new); }