From 9e2e9b44d152d626c56b4813e90dc2b708b79675 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 17 Jun 2024 06:26:30 +0200 Subject: [PATCH 1/7] add some fetch = FetchType.LAZY to 1:1 references --- .../hs/booking/item/HsBookingItemEntity.java | 5 +- .../hosting/asset/HsHostingAssetEntity.java | 13 ++--- ...HsBookingItemControllerAcceptanceTest.java | 47 ++++++++++++------- ...sHostingAssetControllerAcceptanceTest.java | 4 +- ...HostingAssetRepositoryIntegrationTest.java | 18 +++++-- .../test/ContextBasedTestWithCleanup.java | 42 ++++++++++------- 6 files changed, 80 insertions(+), 49 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java index b820c243..6daa8ba9 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java @@ -24,6 +24,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; @@ -82,11 +83,11 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { @Version private int version; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "projectuuid") private HsBookingProjectEntity project; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parentitemuuid") private HsBookingItemEntity parentItem; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java index 3f8202ef..bc77905f 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java @@ -21,6 +21,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; @@ -77,15 +78,15 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { @Version private int version; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "bookingitemuuid") private HsBookingItemEntity bookingItem; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parentassetuuid") private HsHostingAssetEntity parentAsset; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "assignedtoassetuuid") private HsHostingAssetEntity assignedToAsset; @@ -93,12 +94,12 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { @Enumerated(EnumType.STRING) private HsHostingAssetType type; - @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true) + @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name="parentassetuuid", referencedColumnName="uuid") - private List subHostingAssets; + private List subHostingAssets; // FIXME: can only be one @Column(name = "identifier") - private String identifier; // vm1234, xyz00, example.org, xyz00_abc + private String identifier; // e.g. vm1234, xyz00, example.org, xyz00_abc @Column(name = "caption") private String caption; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java index 2804a758..4a572fb0 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java @@ -4,13 +4,17 @@ import io.hypersistence.utils.hibernate.type.range.Range; import io.restassured.RestAssured; import io.restassured.http.ContentType; import net.hostsharing.hsadminng.HsadminNgApplication; -import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestClassOrder; +import org.junit.jupiter.api.TestMethodOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; @@ -33,6 +37,7 @@ import static org.hamcrest.Matchers.matchesRegex; classes = { HsadminNgApplication.class, JpaAttempt.class } ) @Transactional +@TestClassOrder(ClassOrderer.OrderAnnotation.class) // fail early on fetching problems class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup { @LocalServerPort @@ -51,6 +56,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup JpaAttempt jpaAttempt; @Nested + @Order(2) class ListBookingItems { @Test @@ -119,6 +125,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup } @Nested + @Order(3) class AddBookingItem { @Test @@ -170,13 +177,16 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup } @Nested + @Order(1) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class GetBookingItem { @Test + @Order(1) void globalAdmin_canGetArbitraryBookingItem() { context.define("superuser-alex@hostsharing.net"); final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedWebspace").stream() - .filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "fir")) + .filter(bi -> belongsToProject(bi, "D-1000111 default project")) .map(HsBookingItemEntity::getUuid) .findAny().orElseThrow(); @@ -206,10 +216,11 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup } @Test + @Order(2) void normalUser_canNotGetUnrelatedBookingItem() { context.define("superuser-alex@hostsharing.net"); final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedServer").stream() - .filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "sec")) + .filter(bi -> belongsToProject(bi, "D-1000212 default project")) .map(HsBookingItemEntity::getUuid) .findAny().orElseThrow(); @@ -224,23 +235,21 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup } @Test - // TODO.impl: For unknown reason, this test fails in about 50%, not finding the uuid (404), maybe no SELECT permission? + @Order(3) + // TODO.impl: For unknown reason this test fails in about 50%, not finding the uuid (404). void projectAdmin_canGetRelatedBookingItem() { context.define("superuser-alex@hostsharing.net"); - final var givenBookingItemUuid = bookingItemRepo.findByCaption("separate ManagedServer").stream() - .filter(bi -> belongsToDebitorWithDefaultPrefix(bi, "sec")) - .map(HsBookingItemEntity::getUuid) + final var givenBookingItem = bookingItemRepo.findByCaption("separate ManagedServer").stream() + .filter(bi -> belongsToProject(bi, "D-1000313 default project")) .findAny().orElseThrow(); - generateRbacDiagramForObjectPermission(givenBookingItemUuid, "SELECT", "select"); - RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") - .header("assumed-roles", "hs_booking_project#D-1000212-D-1000212defaultproject:ADMIN") + .header("assumed-roles", "hs_booking_project#D-1000313-D-1000313defaultproject:ADMIN") .port(port) .when() - .get("http://localhost/api/hs/booking/items/" + givenBookingItemUuid) + .get("http://localhost/api/hs/booking/items/" + givenBookingItem.getUuid()) .then().log().all().assertThat() .statusCode(200) .contentType("application/json") @@ -260,22 +269,22 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup """)); // @formatter:on } - private static boolean belongsToDebitorWithDefaultPrefix(final HsBookingItemEntity bi, final String defaultPrefix) { + private static boolean belongsToProject(final HsBookingItemEntity bi, final String projectCaption) { return ofNullable(bi) .map(HsBookingItemEntity::getProject) - .map(HsBookingProjectEntity::getDebitor) - .filter(bd -> bd.getDefaultPrefix().equals(defaultPrefix)) + .filter(bp -> bp.getCaption().equals(projectCaption)) .isPresent(); } } @Nested + @Order(4) class PatchBookingItem { @Test void globalAdmin_canPatchAllUpdatablePropertiesOfBookingItem() { - final var givenBookingItem = givenSomeBookingItem(1000111, MANAGED_WEBSPACE, + final var givenBookingItem = givenSomeNewBookingItem(1000111, MANAGED_WEBSPACE, resource("HDD", 100), resource("SSD", 50), resource("Traffic", 250)); RestAssured // @formatter:off @@ -324,12 +333,13 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup } @Nested + @Order(5) class DeleteBookingItem { @Test void globalAdmin_canDeleteArbitraryBookingItem() { context.define("superuser-alex@hostsharing.net"); - final var givenBookingItem = givenSomeBookingItem(1000111, MANAGED_WEBSPACE, + final var givenBookingItem = givenSomeNewBookingItem(1000111, MANAGED_WEBSPACE, resource("HDD", 100), resource("SSD", 50), resource("Traffic", 250)); RestAssured // @formatter:off @@ -348,7 +358,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void normalUser_canNotDeleteUnrelatedBookingItem() { context.define("superuser-alex@hostsharing.net"); - final var givenBookingItem = givenSomeBookingItem(1000111, MANAGED_WEBSPACE, + final var givenBookingItem = givenSomeNewBookingItem(1000111, MANAGED_WEBSPACE, resource("HDD", 100), resource("SSD", 50), resource("Traffic", 250)); RestAssured // @formatter:off @@ -366,10 +376,11 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup } @SafeVarargs - private HsBookingItemEntity givenSomeBookingItem(final int debitorNumber, + private HsBookingItemEntity givenSomeNewBookingItem(final int debitorNumber, final HsBookingItemType hsBookingItemType, final Map.Entry... resources) { return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); + // FIXME: use projectRepo directly final var givenProject = debitorRepo.findDebitorByDebitorNumber(debitorNumber).stream() .map(d -> projectRepo.findAllByDebitorUuid(d.getUuid())) .flatMap(java.util.List::stream) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index e9f8180d..2ea554c6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -458,8 +458,8 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup context.define("superuser-alex@hostsharing.net"); assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get() .matches(asset -> { - assertThat(asset.toString()).isEqualTo( - "HsHostingAssetEntity(MANAGED_SERVER, vm2001, some test-asset, D-1000111:D-1000111 default project:some ManagedServer, { monit_max_cpu_usage: 90, monit_max_ram_usage: 70, monit_max_ssd_usage: 85, monit_min_free_ssd: 5 })"); + assertThat(asset.getConfig().toString()).isEqualTo( + "{ monit_max_cpu_usage: 90, monit_max_ram_usage: 70, monit_max_ssd_usage: 85, monit_min_free_ssd: 5 }"); return true; }); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java index 83560cc9..25f73d9f 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java @@ -24,6 +24,8 @@ import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import static java.util.Map.entry; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; @@ -153,7 +155,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu private void assertThatAssetIsPersisted(final HsHostingAssetEntity saved) { final var found = assetRepo.findByUuid(saved.getUuid()); - assertThat(found).isNotEmpty().map(HsHostingAssetEntity::toString).get().isEqualTo(saved.toString()); + assertThat(found).isNotEmpty().map(HsHostingAssetEntity::getVersion).get().isEqualTo(saved.getVersion()); } } @@ -176,6 +178,14 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu "HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedWebspace)"); } + public R stopWatch(final String caption, final Supplier operation) { + long start = System.nanoTime(); + final R result = operation.get(); + System.out.printf("StopWatch %s: %dms\n", + caption, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)); + return result; + } + @Test public void normalUser_canViewOnlyRelatedAsset() { // given: @@ -184,7 +194,9 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu .findAny().orElseThrow().getUuid(); // when: - final var result = assetRepo.findAllByCriteria(projectUuid, null, null); + final var result = stopWatch("findAllByCriteria", () -> + assetRepo.findAllByCriteria(projectUuid, null, null) + ); // then: exactlyTheseAssetsAreReturned( @@ -240,7 +252,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu private void assertThatAssetActuallyInDatabase(final HsHostingAssetEntity saved) { final var found = assetRepo.findByUuid(saved.getUuid()); assertThat(found).isNotEmpty().get().isNotSameAs(saved) - .extracting(Object::toString).isEqualTo(saved.toString()); + .extracting(HsHostingAssetEntity::getVersion).isEqualTo(saved.getVersion()); } } diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/test/ContextBasedTestWithCleanup.java b/src/test/java/net/hostsharing/hsadminng/rbac/test/ContextBasedTestWithCleanup.java index 154dbb11..98bbdbc6 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/test/ContextBasedTestWithCleanup.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/test/ContextBasedTestWithCleanup.java @@ -63,7 +63,7 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest { return merged; } - // remove HsOfficeCoopAssetsTransactionRawEntity, which is not needed anymore after this change + // FIXME: remove HsOfficeCoopAssetsTransactionRawEntity, which is not needed anymore after this change public UUID toCleanup(final Class entityClass, final UUID uuidToCleanup) { out.println("toCleanup(" + entityClass.getSimpleName() + ", " + uuidToCleanup + ")"); entitiesToCleanup.put(uuidToCleanup, entityClass); @@ -176,7 +176,8 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest { } private void cleanupTemporaryTestData() { - jpaAttempt.transacted(() -> { + // For better performance in a single transaction ... + final var exception = jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net", null); entitiesToCleanup.reversed().forEach((uuid, entityClass) -> { final var rvTableName = entityClass.getAnnotation(Table.class).name(); @@ -188,7 +189,12 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest { .setParameter("uuid", uuid).executeUpdate(); out.println("DELETING temporary " + entityClass.getSimpleName() + "#" + uuid + " deleted " + deletedRows + " rows"); }); - }).assertSuccessful(); + }).caughtException(); + + // ... and in case of foreign key violations, we rely on the RbacObject cleanup. + if (exception != null) { + System.err.println(exception); + } } private long assertNoNewRbacObjectsRolesAndGrantsLeaked() { @@ -214,24 +220,24 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest { } private void deleteLeakedRbacObjects() { - jpaAttempt.transacted(() -> rbacObjectRepo.findAll()).returnedValue().stream() - .filter(o -> o.serialId > latestIntialTestDataSerialId) - .sorted(comparing(o -> o.serialId)) - .forEach(o -> { - final var exception = jpaAttempt.transacted(() -> { - context.define("superuser-alex@hostsharing.net", null); + rbacObjectRepo.findAll().stream() + .filter(o -> o.serialId > latestIntialTestDataSerialId) + .sorted(comparing(o -> o.serialId)) + .forEach(o -> { + final var exception = jpaAttempt.transacted(() -> { + context.define("superuser-alex@hostsharing.net", null); - em.createNativeQuery("DELETE FROM " + o.objectTable + " WHERE uuid=:uuid") - .setParameter("uuid", o.uuid) - .executeUpdate(); + em.createNativeQuery("DELETE FROM " + o.objectTable + " WHERE uuid=:uuid") + .setParameter("uuid", o.uuid) + .executeUpdate(); - out.println("DELETING leaked " + o.objectTable + "#" + o.uuid + " SUCCEEDED"); - }).caughtException(); + out.println("DELETING leaked " + o.objectTable + "#" + o.uuid + " SUCCEEDED"); + }).caughtException(); - if (exception != null) { - out.println("DELETING leaked " + o.objectTable + "#" + o.uuid + " FAILED " + exception); - } - }); + if (exception != null) { + out.println("DELETING leaked " + o.objectTable + "#" + o.uuid + " FAILED " + exception); + } + }); } private void assertEqual(final Set before, final Set after) { -- 2.39.5 From 7e608068e6348f8780c360033a127ebb06db379c Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 17 Jun 2024 09:09:27 +0200 Subject: [PATCH 2/7] avoid lazy-loading necessity in assertion --- .../booking/item/HsBookingItemRepositoryIntegrationTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index 028971ee..fcc290ff 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -231,7 +231,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup private void assertThatBookingItemActuallyInDatabase(final HsBookingItemEntity saved) { final var found = bookingItemRepo.findByUuid(saved.getUuid()); assertThat(found).isNotEmpty().get().isNotSameAs(saved) - .extracting(Object::toString).isEqualTo(saved.toString()); + .extracting(HsBookingItemEntity::getResources) + .extracting(Object::toString) + .isEqualTo(saved.getResources().toString()); } } -- 2.39.5 From b1cf2ed04ac15bed48b5d82a12308151c4502111 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 17 Jun 2024 09:14:44 +0200 Subject: [PATCH 3/7] directly use projectRepo to fetch test-data --- .../item/HsBookingItemControllerAcceptanceTest.java | 13 +++++-------- .../rbac/test/ContextBasedTestWithCleanup.java | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java index 4a572fb0..bf8b4ba9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java @@ -284,7 +284,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void globalAdmin_canPatchAllUpdatablePropertiesOfBookingItem() { - final var givenBookingItem = givenSomeNewBookingItem(1000111, MANAGED_WEBSPACE, + final var givenBookingItem = givenSomeNewBookingItem("D-1000111 default project", MANAGED_WEBSPACE, resource("HDD", 100), resource("SSD", 50), resource("Traffic", 250)); RestAssured // @formatter:off @@ -339,7 +339,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void globalAdmin_canDeleteArbitraryBookingItem() { context.define("superuser-alex@hostsharing.net"); - final var givenBookingItem = givenSomeNewBookingItem(1000111, MANAGED_WEBSPACE, + final var givenBookingItem = givenSomeNewBookingItem("D-1000111 default project", MANAGED_WEBSPACE, resource("HDD", 100), resource("SSD", 50), resource("Traffic", 250)); RestAssured // @formatter:off @@ -358,7 +358,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup @Test void normalUser_canNotDeleteUnrelatedBookingItem() { context.define("superuser-alex@hostsharing.net"); - final var givenBookingItem = givenSomeNewBookingItem(1000111, MANAGED_WEBSPACE, + final var givenBookingItem = givenSomeNewBookingItem("D-1000111 default project", MANAGED_WEBSPACE, resource("HDD", 100), resource("SSD", 50), resource("Traffic", 250)); RestAssured // @formatter:off @@ -376,14 +376,11 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup } @SafeVarargs - private HsBookingItemEntity givenSomeNewBookingItem(final int debitorNumber, + private HsBookingItemEntity givenSomeNewBookingItem(final String projectCaption, final HsBookingItemType hsBookingItemType, final Map.Entry... resources) { return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); - // FIXME: use projectRepo directly - final var givenProject = debitorRepo.findDebitorByDebitorNumber(debitorNumber).stream() - .map(d -> projectRepo.findAllByDebitorUuid(d.getUuid())) - .flatMap(java.util.List::stream) + final var givenProject = projectRepo.findByCaption(projectCaption).stream() .findAny().orElseThrow(); final var newBookingItem = HsBookingItemEntity.builder() .uuid(UUID.randomUUID()) diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/test/ContextBasedTestWithCleanup.java b/src/test/java/net/hostsharing/hsadminng/rbac/test/ContextBasedTestWithCleanup.java index 98bbdbc6..6c9ac849 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/test/ContextBasedTestWithCleanup.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/test/ContextBasedTestWithCleanup.java @@ -63,7 +63,6 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest { return merged; } - // FIXME: remove HsOfficeCoopAssetsTransactionRawEntity, which is not needed anymore after this change public UUID toCleanup(final Class entityClass, final UUID uuidToCleanup) { out.println("toCleanup(" + entityClass.getSimpleName() + ", " + uuidToCleanup + ")"); entitiesToCleanup.put(uuidToCleanup, entityClass); -- 2.39.5 From ead3fa2053e4bfe06cc433b4d706720532266a10 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 17 Jun 2024 09:19:47 +0200 Subject: [PATCH 4/7] cleanup --- .../hsadminng/hs/hosting/asset/HsHostingAssetEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java index bc77905f..164e42d0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java @@ -96,7 +96,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name="parentassetuuid", referencedColumnName="uuid") - private List subHostingAssets; // FIXME: can only be one + private List subHostingAssets; @Column(name = "identifier") private String identifier; // e.g. vm1234, xyz00, example.org, xyz00_abc -- 2.39.5 From 8b0e6863741d76edd76790fe024fcb70b8998981 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 17 Jun 2024 15:40:04 +0200 Subject: [PATCH 5/7] BookingItem to relatedHostingAsset just 1:1 --- .../hs/booking/item/HsBookingItemEntity.java | 9 ++-- ...HsManagedWebspaceBookingItemValidator.java | 49 ++++++++++--------- ...sBookingItemRepositoryIntegrationTest.java | 7 +++ ...gedServerBookingItemValidatorUnitTest.java | 41 ++++++++-------- 4 files changed, 60 insertions(+), 46 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java index 6daa8ba9..1d4acc4c 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java @@ -17,6 +17,8 @@ import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.Type; import jakarta.persistence.CascadeType; @@ -30,6 +32,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import jakarta.persistence.Transient; import jakarta.persistence.Version; @@ -113,9 +116,9 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { @JoinColumn(name="parentitemuuid", referencedColumnName="uuid") private List subBookingItems; - @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true) - @JoinColumn(name="bookingitemuuid", referencedColumnName="uuid") - private List subHostingAssets; + @OneToOne(mappedBy="bookingItem", optional = true) + @NotFound(action = NotFoundAction.IGNORE) + private HsHostingAssetEntity relatedHostingAsset; @Transient private PatchableMapWrapper resourcesWrapper; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java index bf637f15..81c74b9f 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedWebspaceBookingItemValidator.java @@ -7,6 +7,7 @@ import org.apache.commons.lang3.function.TriFunction; import java.util.List; import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_EMAIL_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ADDRESS; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_DATABASE; @@ -38,10 +39,11 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator private static TriFunction> unixUsers() { return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { - final var unixUserCount = entity.getSubHostingAssets().stream() - .flatMap(ha -> ha.getSubHostingAssets().stream()) - .filter(ha -> ha.getType() == UNIX_USER) - .count(); + final var unixUserCount = ofNullable(entity.getRelatedHostingAsset()) + .map(ha -> ha.getSubHostingAssets().stream() + .filter(subAsset -> subAsset.getType() == UNIX_USER) + .count()) + .orElse(0L); final long limitingValue = prop.getValue(entity.getResources()); if (unixUserCount > factor*limitingValue) { return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " unix users, but " + unixUserCount + " found"); @@ -52,13 +54,14 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator private static TriFunction> databaseUsers() { return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { - final var unixUserCount = entity.getSubHostingAssets().stream() - .flatMap(ha -> ha.getSubHostingAssets().stream()) - .filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER ) - .count(); + final var dbUserCount = ofNullable(entity.getRelatedHostingAsset()) + .map(ha -> ha.getSubHostingAssets().stream() + .filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER ) + .count()) + .orElse(0L); final long limitingValue = prop.getValue(entity.getResources()); - if (unixUserCount > factor*limitingValue) { - return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " database users, but " + unixUserCount + " found"); + if (dbUserCount > factor*limitingValue) { + return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " database users, but " + dbUserCount + " found"); } return emptyList(); }; @@ -66,12 +69,13 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator private static TriFunction> databases() { return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { - final var unixUserCount = entity.getSubHostingAssets().stream() - .flatMap(ha -> ha.getSubHostingAssets().stream()) - .filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER ) - .flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream() - .filter(ha -> ha.getType()==PGSQL_DATABASE || ha.getType()==MARIADB_DATABASE)) - .count(); + final var unixUserCount = ofNullable(entity.getRelatedHostingAsset()) + .map(ha -> ha.getSubHostingAssets().stream() + .filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER ) + .flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream() + .filter(subAsset -> subAsset.getType()==PGSQL_DATABASE || subAsset.getType()==MARIADB_DATABASE)) + .count()) + .orElse(0L); final long limitingValue = prop.getValue(entity.getResources()); if (unixUserCount > factor*limitingValue) { return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " databases, but " + unixUserCount + " found"); @@ -82,12 +86,13 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator private static TriFunction> eMailAddresses() { return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { - final var unixUserCount = entity.getSubHostingAssets().stream() - .flatMap(ha -> ha.getSubHostingAssets().stream()) - .filter(bi -> bi.getType() == DOMAIN_EMAIL_SETUP) - .flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream() - .filter(ha -> ha.getType()==EMAIL_ADDRESS)) - .count(); + final var unixUserCount = ofNullable(entity.getRelatedHostingAsset()) + .map(ha -> ha.getSubHostingAssets().stream() + .filter(bi -> bi.getType() == DOMAIN_EMAIL_SETUP) + .flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream() + .filter(subAsset -> subAsset.getType()==EMAIL_ADDRESS)) + .count()) + .orElse(0L); final long limitingValue = prop.getValue(entity.getResources()); if (unixUserCount > factor*limitingValue) { return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " databases, but " + unixUserCount + " found"); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index fcc290ff..3487e20d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -177,6 +177,13 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 })", "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 })", "HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })"); + assertThat(result.stream().filter(bi -> bi.getRelatedHostingAsset()!=null).map(bi -> x(bi)).findAny()) + .as("at least one relatedProject expected, but none found => fetcing relatedProject does not work") + .isNotEmpty(); + } + + HsBookingItemEntity x(HsBookingItemEntity x) { + return x; } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java index 1fe54a82..549b5700 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsManagedServerBookingItemValidatorUnitTest.java @@ -139,27 +139,26 @@ class HsManagedServerBookingItemValidatorUnitTest { entry("Traffic", 1000), entry("Multi", 1) )) - .subHostingAssets(of( - HsHostingAssetEntity.builder() - .type(HsHostingAssetType.MANAGED_WEBSPACE) - .identifier("abc00") - .subHostingAssets(concat( - generate(26, HsHostingAssetType.UNIX_USER, "xyz00-%c%c"), - generateDbUsersWithDatabases(3, HsHostingAssetType.PGSQL_USER, - "xyz00_%c%c", - 1, HsHostingAssetType.PGSQL_DATABASE - ), - generateDbUsersWithDatabases(3, HsHostingAssetType.MARIADB_USER, - "xyz00_%c%c", - 2, HsHostingAssetType.MARIADB_DATABASE - ), - generateDomainEmailSetupsWithEMailAddresses(26, HsHostingAssetType.DOMAIN_EMAIL_SETUP, - "%c%c.example.com", - 10, HsHostingAssetType.EMAIL_ADDRESS - ) - )) - .build() - )) + .relatedHostingAsset(HsHostingAssetEntity.builder() + .type(HsHostingAssetType.MANAGED_WEBSPACE) + .identifier("abc00") + .subHostingAssets(concat( + generate(26, HsHostingAssetType.UNIX_USER, "xyz00-%c%c"), + generateDbUsersWithDatabases(3, HsHostingAssetType.PGSQL_USER, + "xyz00_%c%c", + 1, HsHostingAssetType.PGSQL_DATABASE + ), + generateDbUsersWithDatabases(3, HsHostingAssetType.MARIADB_USER, + "xyz00_%c%c", + 2, HsHostingAssetType.MARIADB_DATABASE + ), + generateDomainEmailSetupsWithEMailAddresses(26, HsHostingAssetType.DOMAIN_EMAIL_SETUP, + "%c%c.example.com", + 10, HsHostingAssetType.EMAIL_ADDRESS + ) + )) + .build() + ) .build(); // when -- 2.39.5 From 875ff5c04687f07ce10bbeac2abccbf8b49ab09a Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 17 Jun 2024 19:11:03 +0200 Subject: [PATCH 6/7] merging master aftermath --- .../HsHostingAssetRepositoryIntegrationTest.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java index 24c0aba7..480f9416 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java @@ -157,7 +157,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu attempt(em, () -> { context("superuser-alex@hostsharing.net"); final var found = assetRepo.findByUuid(saved.getUuid()); - assertThat(found).isNotEmpty().map(HsHostingAssetEntity::getVersion).get().isEqualTo(saved.getVersion()); + assertThat(found).isNotEmpty().map(HsHostingAssetEntity::toString).get().isEqualTo(saved.toString()); }); } } @@ -181,14 +181,6 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu "HsHostingAssetEntity(MANAGED_WEBSPACE, thi01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:D-1000313 default project:separate ManagedWebspace)"); } - public R stopWatch(final String caption, final Supplier operation) { - long start = System.nanoTime(); - final R result = operation.get(); - System.out.printf("StopWatch %s: %dms\n", - caption, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)); - return result; - } - @Test public void normalUser_canViewOnlyRelatedAsset() { // given: @@ -197,9 +189,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu .findAny().orElseThrow().getUuid(); // when: - final var result = stopWatch("findAllByCriteria", () -> - assetRepo.findAllByCriteria(projectUuid, null, null) - ); + final var result = assetRepo.findAllByCriteria(projectUuid, null, null); // then: exactlyTheseAssetsAreReturned( -- 2.39.5 From 75611d70925c313425e4dd906f46412b9cbe71a3 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 17 Jun 2024 19:29:43 +0200 Subject: [PATCH 7/7] cleanup --- .../hsadminng/hs/booking/item/HsBookingItemEntity.java | 3 +-- .../item/HsBookingItemRepositoryIntegrationTest.java | 8 ++------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java index 1d4acc4c..0856f866 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java @@ -116,8 +116,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { @JoinColumn(name="parentitemuuid", referencedColumnName="uuid") private List subBookingItems; - @OneToOne(mappedBy="bookingItem", optional = true) - @NotFound(action = NotFoundAction.IGNORE) + @OneToOne(mappedBy="bookingItem") private HsHostingAssetEntity relatedHostingAsset; @Transient diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index 3487e20d..b474d0c7 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -177,15 +177,11 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 })", "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 })", "HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })"); - assertThat(result.stream().filter(bi -> bi.getRelatedHostingAsset()!=null).map(bi -> x(bi)).findAny()) - .as("at least one relatedProject expected, but none found => fetcing relatedProject does not work") + assertThat(result.stream().filter(bi -> bi.getRelatedHostingAsset()!=null).findAny()) + .as("at least one relatedProject expected, but none found => fetching relatedProject does not work") .isNotEmpty(); } - HsBookingItemEntity x(HsBookingItemEntity x) { - return x; - } - @Test public void normalUser_canViewOnlyRelatedBookingItems() { // given: -- 2.39.5