Compare commits

..

No commits in common. "f379ae5cb248a14287f0f4b79bff1526fb77a0c2" and "24811661d430df7244735c675abe31ab6f68cf7e" have entirely different histories.

10 changed files with 65 additions and 93 deletions

View File

@ -62,7 +62,8 @@ public class HsBookingItemController implements HsBookingItemsApi {
final var entityToSave = mapper.map(body, HsBookingItemEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = HsBookingItemEntityValidatorRegistry.validated(bookingItemRepo.save(entityToSave));
final HsBookingItemEntity entityToSave1 = bookingItemRepo.save(entityToSave);
final var saved = HsBookingItemEntityValidatorRegistry.validated(entityToSave1);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
@ -83,7 +84,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
context.define(currentUser, assumedRoles);
final var result = bookingItemRepo.findByUuid(bookingItemUuid);
result.ifPresent(entity -> em.detach(entity)); // prevent further LAZY-loading
result.ifPresent(entity -> em.detach(entity));
return result
.map(bookingItemEntity -> ResponseEntity.ok(
mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))

View File

@ -21,33 +21,25 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
public List<String> validate(final HsBookingItemEntity bookingItem) {
return sequentiallyValidate(
() -> validateProperties(bookingItem),
() -> optionallyValidate(bookingItem.getParentItem()),
() -> enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources())),
() -> enrich(prefix(bookingItem.toShortString(), "parentItem"), optionallyValidate(bookingItem.getParentItem())),
() -> validateAgainstSubEntities(bookingItem)
);
}
private List<String> validateProperties(final HsBookingItemEntity bookingItem) {
return enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources()));
}
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null
? enrich(prefix(bookingItem.toShortString(), ""),
HsBookingItemEntityValidatorRegistry.doValidate(bookingItem))
: emptyList();
return bookingItem != null ? HsBookingItemEntityValidatorRegistry.doValidate(bookingItem) : emptyList();
}
protected List<String> validateAgainstSubEntities(final HsBookingItemEntity bookingItem) {
return enrich(prefix(bookingItem.toShortString(), "resources"),
Stream.concat(
stream(propertyValidators)
.map(propDef -> propDef.validateTotals(bookingItem))
.flatMap(Collection::stream),
stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(bookingItem, prop))
).filter(Objects::nonNull).toList());
return Stream.concat(
stream(propertyValidators)
.map(propDef -> propDef.validateTotals(bookingItem))
.flatMap(Collection::stream),
stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(bookingItem, prop))
).filter(Objects::nonNull).toList();
}
// TODO.refa: convert into generic shape like multi-options validator
@ -64,13 +56,13 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
final var maxValue = getNonNullIntegerValue(propDef, bookingItem.getResources());
if (propDef.thresholdPercentage() != null ) {
return totalValue > (maxValue * propDef.thresholdPercentage() / 100)
? "%s' maximum total is %d%s, but actual total %s %d%s, which exceeds threshold of %d%%"
.formatted(propName, maxValue, propUnit, propName, totalValue, propUnit, propDef.thresholdPercentage())
? "%s' total is %d%s, thus exceeds max total %s %d%s, which is above threshold of %d%%"
.formatted(propName, totalValue, propUnit, propName, maxValue, propUnit, propDef.thresholdPercentage())
: null;
} else {
return totalValue > maxValue
? "%s' maximum total is %d%s, but actual total %s %d%s"
.formatted(propName, maxValue, propUnit, propName, totalValue, propUnit)
? "%s' total is %d%s, thus exceeds max total %s %d%s"
.formatted(propName, totalValue, propUnit, propName, maxValue, propUnit)
: null;
}
}

View File

@ -21,8 +21,6 @@ import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry.validated;
@RestController
public class HsHostingAssetController implements HsHostingAssetsApi {
@ -63,7 +61,8 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
final var entityToSave = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = validated(assetRepo.save(entityToSave));
final HsHostingAssetEntity persistentEntity = assetRepo.save(entityToSave);
final var saved = HsHostingAssetEntityValidatorRegistry.validated(persistentEntity);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
@ -118,7 +117,8 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
new HsHostingAssetEntityPatcher(current).apply(body);
final var saved = validated(assetRepo.save(current));
final HsHostingAssetEntity persistentEntity = assetRepo.save(current);
final var saved = HsHostingAssetEntityValidatorRegistry.validated(persistentEntity);
final var mapped = mapper.map(saved, HsHostingAssetResource.class);
return ResponseEntity.ok(mapped);
}

View File

@ -23,38 +23,29 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
@Override
public List<String> validate(final HsHostingAssetEntity assetEntity) {
return sequentiallyValidate(
() -> validateProperties(assetEntity),
() -> optionallyValidate(assetEntity.getBookingItem()),
() -> optionallyValidate(assetEntity.getParentAsset()),
() -> validateAgainstSubEntities(assetEntity)
() -> enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig())),
() -> enrich(prefix(assetEntity.toShortString(), "bookingItem"), optionallyValidate(assetEntity.getBookingItem())),
() -> enrich(prefix(assetEntity.toShortString(), "parentAsset"), optionallyValidate(assetEntity.getParentAsset())),
() -> validateSubEntities(assetEntity)
);
}
private List<String> validateProperties(final HsHostingAssetEntity assetEntity) {
return enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig()));
}
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
return assetEntity != null
? enrich(prefix(assetEntity.toShortString(), "parentAsset"),
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validate(assetEntity))
: emptyList();
return assetEntity != null ?
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validate(assetEntity) :
emptyList();
}
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null
? enrich(prefix(bookingItem.toShortString(), "bookingItem"),
HsBookingItemEntityValidatorRegistry.doValidate(bookingItem))
: emptyList();
return bookingItem != null ? HsBookingItemEntityValidatorRegistry.doValidate(bookingItem) : emptyList();
}
protected List<String> validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) {
return enrich(prefix(assetEntity.toShortString(), "config"),
stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(assetEntity, prop))
.filter(Objects::nonNull)
.toList());
protected List<String> validateSubEntities(final HsHostingAssetEntity assetEntity) {
return stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(assetEntity, prop))
.filter(Objects::nonNull)
.toList();
}
private String validateMaxTotalValue(
@ -69,8 +60,8 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
.reduce(0, Integer::sum);
final var maxValue = getNonNullIntegerValue(propDef, hostingAsset.getConfig());
return totalValue > maxValue
? "%s' maximum total is %d%s, but actual total is %s %d%s".formatted(
propName, maxValue, propUnit, propName, totalValue, propUnit)
? "%s' total is %d%s, thus exceeds max total %s %d%s".formatted(
propName, totalValue, propUnit, propName, maxValue, propUnit)
: null;
}
}

View File

@ -38,10 +38,10 @@ class HsBookingItemEntityValidatorUnitTest {
// then
assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining(
"'D-12345:test project:Test-Server.resources.CPUs' is required but missing",
"'D-12345:test project:Test-Server.resources.RAM' is required but missing",
"'D-12345:test project:Test-Server.resources.SSD' is required but missing",
"'D-12345:test project:Test-Server.resources.Traffic' is required but missing");
"D-12345:test project:Test-Server.resources.CPUs' is required but missing",
"D-12345:test project:Test-Server.resources.RAM' is required but missing",
"D-12345:test project:Test-Server.resources.SSD' is required but missing",
"D-12345:test project:Test-Server.resources.Traffic' is required but missing");
}
@Test

View File

@ -109,10 +109,10 @@ class HsCloudServerBookingItemValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:Test Cloud.resources.CPUs' maximum total is 4, but actual total CPUs 5",
"'D-12345:Test-Project:Test Cloud.resources.RAM' maximum total is 20 GB, but actual total RAM 30 GB",
"'D-12345:Test-Project:Test Cloud.resources.SSD' maximum total is 100 GB, but actual total SSD 150 GB",
"'D-12345:Test-Project:Test Cloud.resources.Traffic' maximum total is 5000 GB, but actual total Traffic 5500 GB"
"'D-12345:Test-Project:Test Cloud-Server.parentItem.CPUs' total is 5, thus exceeds max total CPUs 4",
"'D-12345:Test-Project:Test Cloud-Server.parentItem.RAM' total is 30 GB, thus exceeds max total RAM 20 GB",
"'D-12345:Test-Project:Test Cloud-Server.parentItem.SSD' total is 150 GB, thus exceeds max total SSD 100 GB",
"'D-12345:Test-Project:Test Cloud-Server.parentItem.Traffic' total is 5500 GB, thus exceeds max total Traffic 5000 GB"
);
}
}

View File

@ -120,10 +120,10 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:null.resources.CPUs' maximum total is 4, but actual total CPUs 5",
"'D-12345:Test-Project:null.resources.RAM' maximum total is 20 GB, but actual total RAM 30 GB",
"'D-12345:Test-Project:null.resources.SSD' maximum total is 100 GB, but actual total SSD 150 GB",
"'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic 5500 GB"
"'D-12345:Test-Project:null.parentItem.CPUs' total is 5, thus exceeds max total CPUs 4",
"'D-12345:Test-Project:null.parentItem.RAM' total is 30 GB, thus exceeds max total RAM 20 GB",
"'D-12345:Test-Project:null.parentItem.SSD' total is 150 GB, thus exceeds max total SSD 100 GB",
"'D-12345:Test-Project:null.parentItem.Traffic' total is 5500 GB, thus exceeds max total Traffic 5000 GB"
);
}
@ -132,7 +132,6 @@ class HsManagedServerBookingItemValidatorUnitTest {
// given
final var managedWebspaceBookingItem = HsBookingItemEntity.builder()
.type(MANAGED_WEBSPACE)
.project(project)
.caption("test Managed-Webspace")
.resources(ofEntries(
entry("SSD", 100),
@ -167,10 +166,10 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:test Managed-Webspace.resources.Multi=1 allows at maximum 25 unix users, but 26 found",
"'D-12345:Test-Project:test Managed-Webspace.resources.Multi=1 allows at maximum 5 database users, but 6 found",
"'D-12345:Test-Project:test Managed-Webspace.resources.Multi=1 allows at maximum 5 databases, but 9 found",
"'D-12345:Test-Project:test Managed-Webspace.resources.Multi=1 allows at maximum 250 databases, but 260 found"
"Multi=1 allows at maximum 25 unix users, but 26 found",
"Multi=1 allows at maximum 5 database users, but 6 found",
"Multi=1 allows at maximum 5 databases, but 9 found",
"Multi=1 allows at maximum 250 databases, but 260 found"
);
}

View File

@ -1,8 +1,6 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import org.junit.jupiter.api.Test;
import static java.util.List.of;
@ -15,14 +13,6 @@ import static org.assertj.core.api.Assertions.assertThat;
class HsPrivateCloudBookingItemValidatorTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test
void validatesPropertyTotals() {
// given
@ -67,7 +57,6 @@ class HsPrivateCloudBookingItemValidatorTest {
void validatesExceedingPropertyTotals() {
// given
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.project(project)
.type(PRIVATE_CLOUD)
.resources(ofEntries(
entry("CPUs", 4),
@ -102,10 +91,10 @@ class HsPrivateCloudBookingItemValidatorTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:null.resources.CPUs' maximum total is 4, but actual total CPUs 5",
"'D-12345:Test-Project:null.resources.RAM' maximum total is 20 GB, but actual total RAM 30 GB",
"'D-12345:Test-Project:null.resources.SSD' maximum total is 100 GB, but actual total SSD 150 GB",
"'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic 5500 GB"
"CPUs' total is 5, thus exceeds max total CPUs 4",
"RAM' total is 30 GB, thus exceeds max total RAM 20 GB",
"SSD' total is 150 GB, thus exceeds max total SSD 100 GB",
"Traffic' total is 5500 GB, thus exceeds max total Traffic 5000 GB"
);
}

View File

@ -199,10 +199,10 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.extract().header("Location"); // @formatter:on
// finally, the new asset can be accessed under the generated UUID
final var newWebspace = UUID.fromString(
final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newWebspace).isNotNull();
toCleanup(HsHostingAssetEntity.class, newWebspace);
assertThat(newUserUuid).isNotNull();
toCleanup(HsHostingAssetEntity.class, newUserUuid);
}
@Test
@ -334,7 +334,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
{
"statusPhrase": "Bad Request",
"message": "[
<<<'D-1000111:D-1000111 default project:separate ManagedWebspace.resources.Multi=1 allows at maximum 25 unix users, but 26 found
<<<'MANAGED_WEBSPACE:fir01.bookingItem.Multi=1 allows at maximum 25 unix users, but 26 found
<<<]"
}
""".replaceAll(" +<<<", ""))); // @formatter:on

View File

@ -25,8 +25,8 @@ class HsHostingAssetEntityValidatorUnitTest {
// then
assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining(
"'MANAGED_SERVER:vm1234.config.monit_max_ssd_usage' is required but missing",
"'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is required but missing",
"'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is required but missing");
"MANAGED_SERVER:vm1234.config.monit_max_ssd_usage is required but missing",
"MANAGED_SERVER:vm1234.config.monit_max_cpu_usage is required but missing",
"MANAGED_SERVER:vm1234.config.monit_max_ram_usage is required but missing");
}
}