From 0b48e8d1b7c3b48b3a7a90845e8d9acf35c9a3c0 Mon Sep 17 00:00:00 2001
From: Michael Hoennig <michael@hoennig.de>
Date: Tue, 13 Sep 2022 10:58:54 +0200
Subject: [PATCH] consolidating role deletion from business objects to rbac
 system

---
 .../resources/db/changelog/050-rbac-base.sql  | 101 ++++++++++++++++--
 .../db/changelog/113-test-customer-rbac.sql   |  36 +------
 .../db/changelog/123-test-package-rbac.sql    |  36 +------
 .../db/changelog/133-test-domain-rbac.sql     |  37 +------
 .../db/changelog/200-hs-admin-contact.sql     |   2 +-
 .../changelog/203-hs-admin-contact-rbac.sql   |  44 +-------
 .../db/changelog/210-hs-admin-person.sql      |   2 +-
 .../db/changelog/213-hs-admin-person-rbac.sql |  50 +--------
 .../db/changelog/220-hs-admin-partner.sql     |   2 +-
 .../changelog/223-hs-admin-partner-rbac.sql   |  45 +-------
 ...AdminPartnerRepositoryIntegrationTest.java |  43 +++++---
 .../java/net/hostsharing/test/JpaAttempt.java |   1 -
 12 files changed, 136 insertions(+), 263 deletions(-)

diff --git a/src/main/resources/db/changelog/050-rbac-base.sql b/src/main/resources/db/changelog/050-rbac-base.sql
index a9a97342..8196a40a 100644
--- a/src/main/resources/db/changelog/050-rbac-base.sql
+++ b/src/main/resources/db/changelog/050-rbac-base.sql
@@ -122,7 +122,17 @@ create table RbacObject
 
 call create_journal('RbacObject');
 
-create or replace function createRbacObject()
+--//
+
+
+-- ============================================================================
+--changeset rbac-base-GENERATE-RELATED-OBJECT:1 endDelimiter:--//
+-- ----------------------------------------------------------------------------
+
+/*
+    Inserts related RbacObject for use in the BEFORE ONSERT TRIGGERs on the business objects.
+ */
+create or replace function insertRelatedRbacObject()
     returns trigger
     language plpgsql
     strict as $$
@@ -147,8 +157,51 @@ begin
         raise exception 'invalid usage of TRIGGER AFTER INSERT';
     end if;
 end; $$;
+
+/*
+    Deletes related RbacObject for use in the BEFORE DELETE TRIGGERs on the business objects.
+ */
+create or replace function deleteRelatedRbacObject()
+    returns trigger
+    language plpgsql
+    strict as $$
+begin
+    if TG_OP = 'DELETE' then
+        delete from RbacObject where rbacobject.uuid = old.uuid;
+    else
+        raise exception 'invalid usage of TRIGGER BEFORE DELETE';
+    end if;
+    return old;
+end; $$;
+
+create or replace procedure generateRelatedRbacObject(targetTable varchar)
+    language plpgsql as $$
+declare
+    createInsertTriggerSQL text;
+    createDeleteTriggerSQL text;
+begin
+    createInsertTriggerSQL = format($sql$
+        create trigger createRbacObjectFor_%s_Trigger
+            before insert
+            on %s
+            for each row
+                execute procedure insertRelatedRbacObject();
+        $sql$, targetTable, targetTable);
+    execute createInsertTriggerSQL;
+
+    createDeleteTriggerSQL = format($sql$
+        create trigger deleteRbacRulesFor_%s_Trigger
+            before delete
+            on %s
+            for each row
+                execute procedure deleteRelatedRbacObject();
+        $sql$, targetTable, targetTable);
+    execute createDeleteTriggerSQL;
+end; $$;
+
 --//
 
+
 -- ============================================================================
 --changeset rbac-base-ROLE:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
@@ -160,9 +213,9 @@ create type RbacRoleType as enum ('owner', 'admin', 'tenant');
 
 create table RbacRole
 (
-    uuid       uuid primary key references RbacReference (uuid) on delete cascade,
-    objectUuid uuid references RbacObject (uuid) not null,
-    roleType   RbacRoleType                      not null,
+    uuid       uuid primary key references RbacReference (uuid) on delete cascade initially deferred, -- initially deferred
+    objectUuid uuid not null references RbacObject (uuid) initially deferred,
+    roleType   RbacRoleType not null,
     unique (objectUuid, roleType)
 );
 
@@ -269,13 +322,13 @@ $$;
 
 
 -- ============================================================================
---changeset hs-admin-person-rbac-ROLES-REMOVAL:1 endDelimiter:--//
+--changeset rbac-base-BEFORE-DELETE-ROLE-TRIGGER:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
 
 /*
     RbacRole BEFORE DELETE TRIGGER function which deletes all related roles.
  */
-create or replace function deleteRbacGrantsForRbacRole()
+create or replace function deleteRbacGrantsOfRbacRole()
     returns trigger
     language plpgsql
     strict as $$
@@ -291,11 +344,43 @@ end; $$;
 /*
     Installs the RbacRole BEFORE DELETE TRIGGER.
  */
-create trigger deleteRbacGrantsForRbacRole_Trigger
+create trigger deleteRbacGrantsOfRbacRole_Trigger
     before delete
     on RbacRole
     for each row
-execute procedure deleteRbacGrantsForRbacRole();
+execute procedure deleteRbacGrantsOfRbacRole();
+--//
+
+
+-- ============================================================================
+--changeset rbac-base-BEFORE-DELETE-OBJECT-TRIGGER:1 endDelimiter:--//
+-- ----------------------------------------------------------------------------
+
+/*
+    RbacObject BEFORE DELETE TRIGGER function which deletes all related roles.
+ */
+create or replace function deleteRbacRolesOfRbacObject()
+    returns trigger
+    language plpgsql
+    strict as $$
+begin
+    if TG_OP = 'DELETE' then
+        delete from RbacPermission p where p.objectuuid = old.uuid;
+        delete from RbacRole r where r.objectUuid = old.uuid;
+    else
+        raise exception 'invalid usage of TRIGGER BEFORE DELETE';
+    end if;
+    return old;
+end; $$;
+
+/*
+    Installs the RbacRole BEFORE DELETE TRIGGER.
+ */
+create trigger deleteRbacRolesOfRbacObject_Trigger
+    before delete
+    on RbacObject
+    for each row
+        execute procedure deleteRbacRolesOfRbacObject();
 --//
 
 
diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql
index b5600726..21ebfda5 100644
--- a/src/main/resources/db/changelog/113-test-customer-rbac.sql
+++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql
@@ -12,7 +12,7 @@ create trigger createRbacObjectForCustomer_Trigger
     before insert
     on test_customer
     for each row
-execute procedure createRbacObject();
+execute procedure insertRelatedRbacObject();
 --//
 
 -- ============================================================================
@@ -106,40 +106,6 @@ execute procedure createRbacRolesForTestCustomer();
 --//
 
 
--- ============================================================================
---changeset test-customer-rbac-ROLES-REMOVAL:1 endDelimiter:--//
--- ----------------------------------------------------------------------------
-
-/*
-    Deletes the roles and their assignments of a deleted customer for the BEFORE DELETE TRIGGER.
- */
-
-create or replace function deleteRbacRulesForTestCustomer()
-    returns trigger
-    language plpgsql
-    strict as $$
-begin
-    if TG_OP = 'DELETE' then
-        call deleteRole(findRoleId(testCustomerOwner(OLD)));
-        call deleteRole(findRoleId(testCustomerAdmin(OLD)));
-        call deleteRole(findRoleId(testCustomerTenant(OLD)));
-    else
-        raise exception 'invalid usage of TRIGGER BEFORE DELETE';
-    end if;
-end; $$;
-
-/*
-    An BEFORE DELETE TRIGGER which deletes the role structure of a customer.
- */
-
-drop trigger if exists deleteRbacRulesForTestCustomer_Trigger on test_customer;
-create trigger deleteRbacRulesForTestCustomer_Trigger
-    before delete
-    on test_customer
-    for each row
-execute procedure deleteRbacRulesForTestCustomer();
---//
-
 -- ============================================================================
 --changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql
index f0426a9b..15cc0d1b 100644
--- a/src/main/resources/db/changelog/123-test-package-rbac.sql
+++ b/src/main/resources/db/changelog/123-test-package-rbac.sql
@@ -11,7 +11,7 @@ create trigger createRbacObjectForPackage_Trigger
     before insert
     on test_package
     for each row
-execute procedure createRbacObject();
+execute procedure insertRelatedRbacObject();
 --//
 
 
@@ -103,40 +103,6 @@ create trigger createRbacRolesForTestPackage_Trigger
 execute procedure createRbacRolesForTestPackage();
 --//
 
--- ============================================================================
---changeset test-package-rbac-ROLES-REMOVAL:1 endDelimiter:--//
--- ----------------------------------------------------------------------------
-
-/*
-    Deletes the roles and their assignments of a deleted package for the BEFORE DELETE TRIGGER.
- */
-
-create or replace function deleteRbacRulesForTestPackage()
-    returns trigger
-    language plpgsql
-    strict as $$
-begin
-    if TG_OP = 'DELETE' then
-        call deleteRole(findRoleId(testPackageOwner(OLD)));
-        call deleteRole(findRoleId(testPackageAdmin(OLD)));
-        call deleteRole(findRoleId(testPackageTenant(OLD)));
-    else
-        raise exception 'invalid usage of TRIGGER BEFORE DELETE';
-    end if;
-end; $$;
-
-/*
-    An BEFORE DELETE TRIGGER which deletes the role structure of a package.
- */
-
-drop trigger if exists deleteRbacRulesForTestPackage_Trigger on test_package;
-create trigger deleteRbacRulesForTestPackage_Trigger
-    before delete
-    on test_package
-    for each row
-execute procedure deleteRbacRulesForTestPackage();
---//
-
 
 -- ============================================================================
 --changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--//
diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql
index ae526528..b50b5f3d 100644
--- a/src/main/resources/db/changelog/133-test-domain-rbac.sql
+++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql
@@ -11,7 +11,7 @@ create trigger createRbacObjectFortest_domain_Trigger
     before insert
     on test_domain
     for each row
-execute procedure createRbacObject();
+execute procedure insertRelatedRbacObject();
 --//
 
 
@@ -121,41 +121,6 @@ execute procedure createRbacRulesForTestDomain();
 --//
 
 
--- ============================================================================
---changeset test-domain-rbac-ROLES-REMOVAL:1 endDelimiter:--//
--- ----------------------------------------------------------------------------
-
-/*
-    Deletes the roles and their assignments of a deleted domain for the BEFORE DELETE TRIGGER.
- */
-
-create or replace function deleteRbacRulesForTestDomain()
-    returns trigger
-    language plpgsql
-    strict as $$
-begin
-    if TG_OP = 'DELETE' then
-        call deleteRole(findRoleId(testdomainOwner(OLD)));
-        call deleteRole(findRoleId(testdomainAdmin(OLD)));
-        call deleteRole(findRoleId(testdomainTenant(OLD)));
-    else
-        raise exception 'invalid usage of TRIGGER BEFORE DELETE';
-    end if;
-end; $$;
-
-/*
-    An BEFORE DELETE TRIGGER which deletes the role structure of a domain.
- */
-
-drop trigger if exists deleteRbacRulesForTestDomain_Trigger on test_package;
-create trigger deleteRbacRulesForTestDomain_Trigger
-    before delete
-    on test_domain
-    for each row
-execute procedure deleteRbacRulesForTestDomain();
---//
-
-
 -- ============================================================================
 --changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
diff --git a/src/main/resources/db/changelog/200-hs-admin-contact.sql b/src/main/resources/db/changelog/200-hs-admin-contact.sql
index a3eb99c2..38ec7a98 100644
--- a/src/main/resources/db/changelog/200-hs-admin-contact.sql
+++ b/src/main/resources/db/changelog/200-hs-admin-contact.sql
@@ -6,7 +6,7 @@
 
 create table if not exists hs_admin_contact
 (
-    uuid           uuid unique references RbacObject (uuid) on delete cascade,
+    uuid           uuid unique references RbacObject (uuid) initially deferred,
     label          varchar(96) not null,
     postalAddress  text,
     emailAddresses text, -- TODO.feat: change to json
diff --git a/src/main/resources/db/changelog/203-hs-admin-contact-rbac.sql b/src/main/resources/db/changelog/203-hs-admin-contact-rbac.sql
index d693f053..36fe3949 100644
--- a/src/main/resources/db/changelog/203-hs-admin-contact-rbac.sql
+++ b/src/main/resources/db/changelog/203-hs-admin-contact-rbac.sql
@@ -1,19 +1,12 @@
 --liquibase formatted sql
 
 -- ============================================================================
---changeset hs-admin-contact-rbac-CREATE-OBJECT:1 endDelimiter:--//
+--changeset hs-admin-contact-rbac-OBJECT:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
-
-/*
-    Creates the related RbacObject through a BEFORE INSERT TRIGGER.
- */
-create trigger createRbacObjectForHsAdminCustomer_Trigger
-    before insert
-    on hs_admin_contact
-    for each row
-execute procedure createRbacObject();
+call generateRelatedRbacObject('hs_admin_contact');
 --//
 
+
 -- ============================================================================
 --changeset hs-admin-contact-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
@@ -103,37 +96,6 @@ execute procedure createRbacRolesForHsAdminContact();
 --//
 
 
--- ============================================================================
---changeset hs-admin-contact-rbac-ROLES-REMOVAL:1 endDelimiter:--//
--- ----------------------------------------------------------------------------
-
-/*
-    Deletes the roles and their assignments of a deleted contact for the BEFORE DELETE TRIGGER.
- */
-create or replace function deleteRbacRulesForHsAdminContact()
-    returns trigger
-    language plpgsql
-    strict as $$
-begin
-    if TG_OP = 'DELETE' then
-        call deleteRole(findRoleId(hsAdminContactOwner(OLD)));
-        call deleteRole(findRoleId(hsAdminContactTenant(OLD)));
-    else
-        raise exception 'invalid usage of TRIGGER BEFORE DELETE';
-    end if;
-    return old;
-end; $$;
-
-/*
-    An BEFORE DELETE TRIGGER which deletes the role structure of a contact.
- */
-create trigger deleteRbacRulesForTestContact_Trigger
-    before delete
-    on hs_admin_contact
-    for each row
-execute procedure deleteRbacRulesForHsAdminContact();
---//
-
 -- ============================================================================
 --changeset hs-admin-contact-rbac-IDENTITY-VIEW:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
diff --git a/src/main/resources/db/changelog/210-hs-admin-person.sql b/src/main/resources/db/changelog/210-hs-admin-person.sql
index 10bcad61..99a51784 100644
--- a/src/main/resources/db/changelog/210-hs-admin-person.sql
+++ b/src/main/resources/db/changelog/210-hs-admin-person.sql
@@ -10,7 +10,7 @@ CREATE CAST (character varying as HsAdminPersonType) WITH INOUT AS IMPLICIT;
 
 create table if not exists hs_admin_person
 (
-    uuid           uuid unique references RbacObject (uuid) on delete cascade,
+    uuid           uuid unique references RbacObject (uuid) initially deferred,
     personType     HsAdminPersonType not null,
     tradeName      varchar(96),
     givenName      varchar(48),
diff --git a/src/main/resources/db/changelog/213-hs-admin-person-rbac.sql b/src/main/resources/db/changelog/213-hs-admin-person-rbac.sql
index 6064e81e..ac58467e 100644
--- a/src/main/resources/db/changelog/213-hs-admin-person-rbac.sql
+++ b/src/main/resources/db/changelog/213-hs-admin-person-rbac.sql
@@ -1,19 +1,13 @@
 --liquibase formatted sql
 
--- ============================================================================
---changeset hs-admin-person-rbac-CREATE-OBJECT:1 endDelimiter:--//
--- ----------------------------------------------------------------------------
 
-/*
-    Creates the related RbacObject through a BEFORE INSERT TRIGGER.
- */
-create trigger createRbacObjectForHsAdminPerson_Trigger
-    before insert
-    on hs_admin_person
-    for each row
-execute procedure createRbacObject();
+-- ============================================================================
+--changeset hs-admin-person-rbac-OBJECT:1 endDelimiter:--//
+-- ----------------------------------------------------------------------------
+call generateRelatedRbacObject('hs_admin_person');
 --//
 
+
 -- ============================================================================
 --changeset hs-admin-person-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
@@ -103,40 +97,6 @@ execute procedure createRbacRolesForHsAdminPerson();
 --//
 
 
--- ============================================================================
---changeset hs-admin-person-rbac-ROLES-REMOVAL:1 endDelimiter:--//
--- ----------------------------------------------------------------------------
-
--- TODO: can we replace all these delete triggers by a delete trigger on RbacObject?
-
-/*
-    Deletes the roles and their assignments of a deleted person for the BEFORE DELETE TRIGGER.
- */
-create or replace function deleteRbacRulesForHsAdminPerson()
-    returns trigger
-    language plpgsql
-    strict as $$
-begin
-    if TG_OP = 'DELETE' then
-        call deleteRole(findRoleId(hsAdminPersonOwner(OLD)));
-        call deleteRole(findRoleId(hsAdminPersonAdmin(OLD)));
-        call deleteRole(findRoleId(hsAdminPersonTenant(OLD)));
-    else
-        raise exception 'invalid usage of TRIGGER BEFORE DELETE';
-    end if;
-    return old;
-end; $$;
-
-/*
-    An BEFORE DELETE TRIGGER which deletes the role structure of a person.
- */
-create trigger deleteRbacRulesForTestPerson_Trigger
-    before delete
-    on hs_admin_person
-    for each row
-execute procedure deleteRbacRulesForHsAdminPerson();
---//
-
 -- ============================================================================
 --changeset hs-admin-person-rbac-IDENTITY-VIEW:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
diff --git a/src/main/resources/db/changelog/220-hs-admin-partner.sql b/src/main/resources/db/changelog/220-hs-admin-partner.sql
index 740c757f..0760b5aa 100644
--- a/src/main/resources/db/changelog/220-hs-admin-partner.sql
+++ b/src/main/resources/db/changelog/220-hs-admin-partner.sql
@@ -6,7 +6,7 @@
 
 create table if not exists hs_admin_partner
 (
-    uuid                uuid unique references RbacObject (uuid) on delete cascade,
+    uuid                uuid unique references RbacObject (uuid) initially deferred, -- on delete cascade
     personUuid          uuid not null references hs_admin_person(uuid),
     contactUuid         uuid not null references hs_admin_contact(uuid),
     registrationOffice  varchar(96),
diff --git a/src/main/resources/db/changelog/223-hs-admin-partner-rbac.sql b/src/main/resources/db/changelog/223-hs-admin-partner-rbac.sql
index c858c6e9..c627c525 100644
--- a/src/main/resources/db/changelog/223-hs-admin-partner-rbac.sql
+++ b/src/main/resources/db/changelog/223-hs-admin-partner-rbac.sql
@@ -1,19 +1,12 @@
 --liquibase formatted sql
 
 -- ============================================================================
---changeset hs-admin-partner-rbac-CREATE-OBJECT:1 endDelimiter:--//
+--changeset hs-admin-partner-rbac-OBJECT:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
-
-/*
-    Creates the related RbacObject through a BEFORE INSERT TRIGGER.
- */
-create trigger createRbacObjectForHsAdminPartner_Trigger
-    before insert
-    on hs_admin_partner
-    for each row
-execute procedure createRbacObject();
+call generateRelatedRbacObject('hs_admin_partner');
 --//
 
+
 -- ============================================================================
 --changeset hs-admin-partner-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
@@ -106,38 +99,6 @@ execute procedure createRbacRolesForHsAdminPartner();
 --//
 
 
--- ============================================================================
---changeset hs-admin-partner-rbac-ROLES-REMOVAL:1 endDelimiter:--//
--- ----------------------------------------------------------------------------
-
-/*
-    Deletes the roles and their assignments of a deleted partner for the BEFORE DELETE TRIGGER.
- */
-create or replace function deleteRbacRulesForHsAdminPartner()
-    returns trigger
-    language plpgsql
-    strict as $$
-begin
-    if TG_OP = 'DELETE' then
-        call deleteRole(findRoleId(hsAdminPartnerOwner(OLD)));
-        call deleteRole(findRoleId(hsAdminPartnerAdmin(OLD)));
-        call deleteRole(findRoleId(hsAdminPartnerTenant(OLD)));
-    else
-        raise exception 'invalid usage of TRIGGER BEFORE DELETE';
-    end if;
-    return old;
-end; $$;
-
-/*
-    An BEFORE DELETE TRIGGER which deletes the role structure of a partner.
- */
-create trigger deleteRbacRulesForTestPartner_Trigger
-    before delete
-    on hs_admin_partner
-    for each row
-execute procedure deleteRbacRulesForHsAdminPartner();
---//
-
 -- ============================================================================
 --changeset hs-admin-partner-rbac-IDENTITY-VIEW:1 endDelimiter:--//
 -- ----------------------------------------------------------------------------
diff --git a/src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java
index 04aa6481..5a7e84df 100644
--- a/src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java
+++ b/src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java
@@ -95,9 +95,9 @@ class HsAdminPartnerRepositoryIntegrationTest extends ContextBasedTest {
         public void createsAndGrantsRoles() {
             // given
             context("alex@hostsharing.net");
-            final var initialRoleCount = rawRoleRepo.findAll().size();
+            final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll());
             final var initialGrantCount = rawGrantRepo.findAll().size();
-            final var initialGrantsDisplayNames = grantDisplaysOf(rawGrantRepo.findAll());
+            final var initialGrantsDisplayNames = grantDisplaysOf(rawGrantRepo.findAll()); // TODO
 
             // when
             attempt(em, () -> {
@@ -112,12 +112,11 @@ class HsAdminPartnerRepositoryIntegrationTest extends ContextBasedTest {
             });
 
             // then
-            assertThat(roleNamesOf(rawRoleRepo.findAll())).containsAll(List.of(
-                            "hs_admin_partner#ErbenBesslerMelBessler-forthcontact.admin",
-                            "hs_admin_partner#ErbenBesslerMelBessler-forthcontact.owner",
-                            "hs_admin_partner#ErbenBesslerMelBessler-forthcontact.tenant"))
-                    .as("invalid number of roles created")
-                    .hasSize(initialRoleCount + 3);
+            assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from(
+                    initialRoleNames,
+                    "hs_admin_partner#ErbenBesslerMelBessler-forthcontact.admin",
+                    "hs_admin_partner#ErbenBesslerMelBessler-forthcontact.owner",
+                    "hs_admin_partner#ErbenBesslerMelBessler-forthcontact.tenant"));
             assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsAll(List.of(
                             "{ grant role hs_admin_partner#ErbenBesslerMelBessler-forthcontact.owner to role global#global.admin by system and assume }",
                             "{ grant role hs_admin_partner#ErbenBesslerMelBessler-forthcontact.tenant to role hs_admin_contact#forthcontact.admin by system and assume }",
@@ -245,19 +244,29 @@ class HsAdminPartnerRepositoryIntegrationTest extends ContextBasedTest {
             final var result = jpaAttempt.transacted(() -> {
                 context("alex@hostsharing.net");
                 partnerRepo.deleteByUuid(givenPartner.getUuid());
-            }).assertSuccessful();
+            });
 
             // then
-            final var roles = rawRoleRepo.findAll();
-            assertThat(roleNamesOf(roles)).containsExactlyInAnyOrder(initialRoleNames);
+            result.assertSuccessful();
+            jpaAttempt.transacted(() -> {
+                final var remainingPartner = em.createNativeQuery("select p.uuid from hs_admin_partner p where p.uuid=?1")
+                        .setParameter(1, givenPartner.getUuid()).getResultList();
+                assertThat(remainingPartner).isEmpty();
+                final var remainingObject = em.createNativeQuery("select o.uuid from RbacObject o where o.uuid=?1")
+                        .setParameter(1, givenPartner.getUuid())
+                        .getResultList();
+                assertThat(remainingObject).isEmpty();
 
-            context("customer-admin@forthcontact.example.com");
-            assertThat(grantDisplaysOf(rawGrantRepo.findAll())).doesNotContain(
-                    "{ grant assumed role hs_admin_contact#forthcontact.owner to user customer-admin@forthcontact.example.com by role global#global.admin }");
+                assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(initialRoleNames);
 
-            context("person-ErbenBesslerMelBessler@example.com");
-            assertThat(grantDisplaysOf(rawGrantRepo.findAll())).doesNotContain(
-                    "{ grant assumed role hs_admin_person#ErbenBesslerMelBessler.owner to user person-ErbenBesslerMelBessl@example.com by role global#global.admin }");
+                context("customer-admin@forthcontact.example.com");
+                assertThat(grantDisplaysOf(rawGrantRepo.findAll())).doesNotContain(
+                        "{ grant assumed role hs_admin_contact#forthcontact.owner to user customer-admin@forthcontact.example.com by role global#global.admin }");
+
+                context("person-ErbenBesslerMelBessler@example.com");
+                assertThat(grantDisplaysOf(rawGrantRepo.findAll())).doesNotContain(
+                        "{ grant assumed role hs_admin_person#ErbenBesslerMelBessler.owner to user person-ErbenBesslerMelBessl@example.com by role global#global.admin }");
+            }).assertSuccessful();
         }
     }
 
diff --git a/src/test/java/net/hostsharing/test/JpaAttempt.java b/src/test/java/net/hostsharing/test/JpaAttempt.java
index 8f62c50f..7ffb270d 100644
--- a/src/test/java/net/hostsharing/test/JpaAttempt.java
+++ b/src/test/java/net/hostsharing/test/JpaAttempt.java
@@ -71,7 +71,6 @@ public class JpaAttempt {
             });
             return JpaResult.forVoidValue();
         } catch (final RuntimeException exc) {
-
             return new JpaResult<>(null, exc);
         }
     }