DSL for hierarchical validation for booking-items and improved violation error messages

This commit is contained in:
Michael Hoennig 2024-06-10 17:32:19 +02:00
parent eb98ab99be
commit 1d2a65ac22
27 changed files with 533 additions and 420 deletions

View File

@ -17,7 +17,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.valid; import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.valid;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
@RestController @RestController

View File

@ -10,7 +10,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.validation.Validatable;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -65,7 +64,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatable<HsBookingItemEntity, HsBookingItemType> { public class HsBookingItemEntity implements Stringifyable, RbacObject {
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class) private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
.withProp(HsBookingItemEntity::getProject) .withProp(HsBookingItemEntity::getProject)
@ -157,16 +156,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
return parentItem == null ? null : parentItem.relatedProject(); return parentItem == null ? null : parentItem.relatedProject();
} }
@Override
public String getPropertiesName() {
return "resources";
}
@Override
public Map<String, Object> getProperties() {
return resources;
}
public HsBookingProjectEntity getRelatedProject() { public HsBookingProjectEntity getRelatedProject() {
return project != null ? project : parentItem.getRelatedProject(); return project != null ? project : parentItem.getRelatedProject();
} }

View File

@ -0,0 +1,120 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import jakarta.validation.ValidationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingItemEntity> {
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity>> validators = new HashMap<>();
static {
register(PRIVATE_CLOUD, new HsPrivateCloudBookingItemValidator());
register(CLOUD_SERVER, new HsCloudServerBookingItemValidator());
register(MANAGED_SERVER, new HsManagedServerBookingItemValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceBookingItemValidator());
}
private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItemEntity> validator) {
stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator));
});
validators.put(type, validator);
}
public static HsEntityValidator<HsBookingItemEntity> forType(final Enum<HsBookingItemType> type) {
if ( validators.containsKey(type)) {
return validators.get(type);
}
throw new IllegalArgumentException("no validator found for type " + type);
}
public static Set<Enum<HsBookingItemType>> types() {
return validators.keySet();
}
public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) {
final var violations = doValidate(entityToSave);
if (!violations.isEmpty()) {
throw new ValidationException(violations.toString());
}
return entityToSave;
}
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
return HsBookingItemEntityValidator.forType(bookingItem.getType()).validate(bookingItem);
}
public HsBookingItemEntityValidator(final ValidatableProperty<?>... properties) {
super(properties);
}
public List<String> validate(final HsBookingItemEntity bookingItem) {
return sequentiallyValidate(
() -> enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources())),
() -> enrich(prefix(bookingItem.toShortString(), "parentItem"), optionallyValidate(bookingItem.getParentItem())),
() -> validateSubEntities(bookingItem)
);
}
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null ? HsBookingItemEntityValidator.doValidate(bookingItem) : emptyList();
}
protected List<String> validateSubEntities(final HsBookingItemEntity bookingItem) {
return stream(propertyValidators)
.filter(propDef -> propDef.isTotalsValidator())
.map(prop -> validateMaxTotalValue(bookingItem, prop))
.filter(Objects::nonNull)
.toList();
}
@SafeVarargs
private List<String> sequentiallyValidate(final Supplier<List<String>>... validators) {
return stream(validators)
.map(Supplier::get)
.filter(violations -> !violations.isEmpty())
.findFirst()
.orElse(emptyList());
}
private String validateMaxTotalValue(
final HsBookingItemEntity bookingItem,
final ValidatableProperty<?> propDef) {
final var propName = propDef.propertyName();
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");
final var totalValue = ofNullable(bookingItem.getSubBookingItems()).orElse(emptyList())
.stream()
.map(subItem -> propDef.getValue(subItem.getResources()))
.map(HsBookingItemEntityValidator::toInteger)
.reduce(0, Integer::sum);
final var maxValue = toInteger(propDef.getValue(bookingItem.getResources()));
return totalValue > maxValue
? "total %s is %d%s exceeds max total %s %d%s".formatted(
propName, totalValue, propUnit, propName, maxValue, propUnit)
: null;
}
private static Integer toInteger(final Object value) {
if (value instanceof Integer) {
return (Integer) value;
}
throw new IllegalArgumentException("Integer value expected, but got " + value);
}
}

View File

@ -1,57 +0,0 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import lombok.experimental.UtilityClass;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import jakarta.validation.ValidationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Arrays.stream;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
@UtilityClass
public class HsBookingItemEntityValidators {
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity, HsBookingItemType>> validators = new HashMap<>();
static {
register(PRIVATE_CLOUD, new HsPrivateCloudBookingItemValidator());
register(CLOUD_SERVER, new HsCloudServerBookingItemValidator());
register(MANAGED_SERVER, new HsManagedServerBookingItemValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceBookingItemValidator());
}
private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItemEntity, HsBookingItemType> validator) {
stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator));
});
validators.put(type, validator);
}
public static HsEntityValidator<HsBookingItemEntity, HsBookingItemType> forType(final Enum<HsBookingItemType> type) {
return validators.get(type);
}
public static Set<Enum<HsBookingItemType>> types() {
return validators.keySet();
}
public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) {
final var violations = validate(entityToSave);
if (!violations.isEmpty()) {
throw new ValidationException(violations.toString());
}
return entityToSave;
}
public static List<String> validate(final HsBookingItemEntity bookingItem) {
return HsBookingItemEntityValidators.forType(bookingItem.getType()).validate(bookingItem);
}
}

View File

@ -1,15 +1,13 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import java.util.List; import java.util.List;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty; import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsCloudServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> { class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator {
HsCloudServerBookingItemValidator() { HsCloudServerBookingItemValidator() {
super( super(

View File

@ -1,16 +1,14 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import java.util.List; import java.util.List;
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty; import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty; import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsManagedServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> { class HsManagedServerBookingItemValidator extends HsBookingItemEntityValidator {
HsManagedServerBookingItemValidator() { HsManagedServerBookingItemValidator() {
super( super(

View File

@ -1,24 +1,47 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import java.util.List;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static java.util.Collections.emptyList;
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty; class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator {
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
class HsManagedWebspaceBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
public HsManagedWebspaceBookingItemValidator() { public HsManagedWebspaceBookingItemValidator() {
super( super(
integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(), integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(),
integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(), integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(),
integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(), integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(),
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").optional(), integerProperty("MultiOptions").min(1).max(100).step(1).required()
.asTotalLimitFor( 25, HsManagedWebspaceBookingItemValidator::totalUnixUsers)
.asTotalLimitFor( 5, HsManagedWebspaceBookingItemValidator::totalDatabaseUsers)
.asTotalLimitFor( 5, HsManagedWebspaceBookingItemValidator::totalDatabases)
.asTotalLimitFor(250, HsManagedWebspaceBookingItemValidator::totalEMailAddresses),
integerProperty("Daemons").min(0).max(10).optional(), integerProperty("Daemons").min(0).max(10).optional(),
booleanProperty("Online Office Server").optional() booleanProperty("Online Office Server").optional(),
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").optional()
); );
} }
private static List<String> totalUnixUsers() {
// FIXME: implement
return emptyList();
}
private static List<String> totalDatabaseUsers() {
// FIXME: implement
return emptyList();
}
private static List<String> totalDatabases() {
// FIXME: implement
return emptyList();
}
private static List<String> totalEMailAddresses() {
// FIXME: implement
return emptyList();
}
} }

View File

@ -1,37 +1,18 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import java.util.List; class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator {
import static java.util.EnumSet.of;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
class HsPrivateCloudBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
HsPrivateCloudBookingItemValidator() { HsPrivateCloudBookingItemValidator() {
super( super(
integerProperty("CPUs").min(4).max(128).required(), integerProperty("CPUs").min(4).max(128).required().asTotalLimit(),
integerProperty("RAM").unit("GB").min(4).max(512).required(), integerProperty("RAM").unit("GB").min(4).max(512).required().asTotalLimit(),
integerProperty("SSD").unit("GB").min(100).max(4000).step(25).required(), integerProperty("SSD").unit("GB").min(100).max(4000).step(25).required().asTotalLimit(),
integerProperty("HDD").unit("GB").min(0).max(16000).step(25).optional(), integerProperty("HDD").unit("GB").min(0).max(16000).step(25).withDefault(0).asTotalLimit(),
integerProperty("Traffic").unit("GB").min(1000).max(40000).step(250).required(), integerProperty("Traffic").unit("GB").min(1000).max(40000).step(250).required().asTotalLimit(),
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional() enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC")
); );
} }
@Override
public List<String> validate(final HsBookingItemEntity privateCloudBookingItem) {
final var selfValidation = super.validate(privateCloudBookingItem);
return !selfValidation.isEmpty() ? selfValidation : validateSubEntities(privateCloudBookingItem);
}
private List<String> validateSubEntities(final HsBookingItemEntity privateCloudBookingItem) {
return validateSubEntities(privateCloudBookingItem, of(CLOUD_SERVER, MANAGED_SERVER));
}
} }

View File

@ -8,7 +8,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.validation.Validatable;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -56,7 +55,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validatable<HsHostingAssetEntity, HsHostingAssetType> { public class HsHostingAssetEntity implements Stringifyable, RbacObject {
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class) private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
.withProp(HsHostingAssetEntity::getType) .withProp(HsHostingAssetEntity::getType)
@ -114,16 +113,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg); PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg);
} }
@Override
public String getPropertiesName() {
return "config";
}
@Override
public Map<String, Object> getProperties() {
return config;
}
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);

View File

@ -1,10 +1,11 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators; import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import org.apache.commons.collections4.ListUtils; import org.apache.commons.collections4.ListUtils;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
@ -19,23 +20,23 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOU
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> { public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity> {
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType>> validators = new HashMap<>(); private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity>> validators = new HashMap<>();
static { static {
register(CLOUD_SERVER, new HsEntityValidator<>()); register(CLOUD_SERVER, new HsHostingAssetEntityValidator());
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator()); register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator()); register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
} }
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> validator) { private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity> validator) {
stream(validator.propertyValidators).forEach( entry -> { stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator)); entry.verifyConsistency(Map.entry(type, validator));
}); });
validators.put(type, validator); validators.put(type, validator);
} }
public static HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> forType(final Enum<HsHostingAssetType> type) { public static HsEntityValidator<HsHostingAssetEntity> forType(final Enum<HsHostingAssetType> type) {
return validators.get(type); return validators.get(type);
} }
@ -51,9 +52,15 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
return entityToSave; return entityToSave;
} }
@SafeVarargs
public HsHostingAssetEntityValidator(final ValidatableProperty<?>... properties) {
super(properties);
}
@Override @Override
public List<String> validate(final HsHostingAssetEntity assetEntity) { public List<String> validate(final HsHostingAssetEntity assetEntity) {
final var selfValidation = super.validate(assetEntity); final var selfValidation = enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig()));
return selfValidation.isEmpty() return selfValidation.isEmpty()
// higher levels are only validated with valid sub-entity // higher levels are only validated with valid sub-entity
? ListUtils.union( ? ListUtils.union(
@ -67,6 +74,6 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
} }
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) { private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null ? HsBookingItemEntityValidators.validate(bookingItem) : emptyList(); return bookingItem != null ? HsBookingItemEntityValidator.doValidate(bookingItem) : emptyList();
} }
} }

View File

@ -1,12 +1,8 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty; class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator {
class HsManagedServerHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
public HsManagedServerHostingAssetValidator() { public HsManagedServerHostingAssetValidator() {
super( super(

View File

@ -1,13 +1,11 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import java.util.List; import java.util.List;
class HsManagedWebspaceHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> { class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator {
public HsManagedWebspaceHostingAssetValidator() { public HsManagedWebspaceHostingAssetValidator() {
} }

View File

@ -8,29 +8,29 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
@Setter @Setter
public class BooleanPropertyValidator extends HsPropertyValidator<Boolean> { public class BooleanProperty extends ValidatableProperty<Boolean> {
private Map.Entry<String, String> falseIf; private Map.Entry<String, String> falseIf;
private BooleanPropertyValidator(final String propertyName) { private BooleanProperty(final String propertyName) {
super(Boolean.class, propertyName); super(Boolean.class, propertyName);
} }
public static BooleanPropertyValidator booleanProperty(final String propertyName) { public static BooleanProperty booleanProperty(final String propertyName) {
return new BooleanPropertyValidator(propertyName); return new BooleanProperty(propertyName);
} }
public HsPropertyValidator<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) { public ValidatableProperty<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) {
this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue); this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue);
return this; return this;
} }
@Override @Override
protected void validate(final ArrayList<String> result, final String propertiesName, final Boolean propValue, final Map<String, Object> props) { protected void validate(final ArrayList<String> result, final Boolean propValue, final Map<String, Object> props) {
if (falseIf != null && !Objects.equals(props.get(falseIf.getKey()), falseIf.getValue())) { if (falseIf != null && !Objects.equals(props.get(falseIf.getKey()), falseIf.getValue())) {
if (propValue) { if (propValue) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be false because " + result.add(propertyName + " is expected to be false because " +
propertiesName+"." + falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue); falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue);
} }
} }
} }

View File

@ -0,0 +1,38 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
@Setter
public class EnumerationProperty extends ValidatableProperty<String> {
private String[] values;
private EnumerationProperty(final String propertyName) {
super(String.class, propertyName);
}
public static EnumerationProperty enumerationProperty(final String propertyName) {
return new EnumerationProperty(propertyName);
}
public ValidatableProperty<String> values(final String... values) {
this.values = values;
return this;
}
@Override
protected void validate(final ArrayList<String> result, final String propValue, final Map<String, Object> props) {
if (Arrays.stream(values).noneMatch(v -> v.equals(propValue))) {
result.add(propertyName + " is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
}
}
@Override
protected String simpleTypeName() {
return "enumeration";
}
}

View File

@ -1,38 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
@Setter
public class EnumerationPropertyValidator extends HsPropertyValidator<String> {
private String[] values;
private EnumerationPropertyValidator(final String propertyName) {
super(String.class, propertyName);
}
public static EnumerationPropertyValidator enumerationProperty(final String propertyName) {
return new EnumerationPropertyValidator(propertyName);
}
public HsPropertyValidator<String> values(final String... values) {
this.values = values;
return this;
}
@Override
protected void validate(final ArrayList<String> result, final String propertiesName, final String propValue, final Map<String, Object> props) {
if (Arrays.stream(values).noneMatch(v -> v.equals(propValue))) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
}
}
@Override
protected String simpleTypeName() {
return "enumeration";
}
}

View File

@ -4,41 +4,36 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
public class HsEntityValidator<E extends Validatable<E, T>, T extends Enum<T>> { public abstract class HsEntityValidator<E> {
public final HsPropertyValidator<?>[] propertyValidators; public final ValidatableProperty<?>[] propertyValidators;
public HsEntityValidator(final HsPropertyValidator<?>... validators) { public HsEntityValidator(final ValidatableProperty<?>... validators) {
propertyValidators = validators; propertyValidators = validators;
} }
public List<String> validate(final E assetEntity) { protected static List<String> enrich(final String prefix, final List<String> messages) {
final var result = new ArrayList<String>(); return messages.stream()
assetEntity.getProperties().keySet().forEach( givenPropName -> { .map(message -> prefix + "." + message)
if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) { .toList();
result.add("'"+assetEntity.getPropertiesName()+"." + givenPropName + "' is not expected but is set to '" +assetEntity.getProperties().get(givenPropName) + "'");
}
});
stream(propertyValidators).forEach(pv -> {
result.addAll(pv.validate(assetEntity.getPropertiesName(), assetEntity.getProperties()));
});
return result;
} }
protected static String prefix(final String... parts) {
return String.join(".", parts);
}
public abstract List<String> validate(final E entity);
public final List<Map<String, Object>> properties() { public final List<Map<String, Object>> properties() {
final var mapper = new ObjectMapper(); final var mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
@ -48,45 +43,27 @@ public class HsEntityValidator<E extends Validatable<E, T>, T extends Enum<T>> {
.toList(); .toList();
} }
protected ArrayList<String> validateProperties(final Map<String, Object> properties) {
final var result = new ArrayList<String>();
properties.keySet().forEach( givenPropName -> {
if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) {
result.add(givenPropName + " is not expected but is set to '" + properties.get(givenPropName) + "'");
}
});
stream(propertyValidators).forEach(pv -> {
result.addAll(pv.validate(properties));
});
return result;
}
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
private static Map<String, Object> asKeyValueMap(final Map map) { private static Map<String, Object> asKeyValueMap(final Map map) {
return (Map<String, Object>) map; return (Map<String, Object>) map;
} }
protected List<String> validateSubEntities(
final HsBookingItemEntity assetEntity,
final EnumSet<HsBookingItemType> subItemTypes) {
return properties().stream()
.map(prop -> validateMaxTotalValue(assetEntity, subItemTypes, prop))
.filter(Objects::nonNull)
.toList();
}
protected String validateMaxTotalValue(
final HsBookingItemEntity assetEntity,
final EnumSet<HsBookingItemType> subTypes,
final Map<String, Object> propDef) {
final var propName = propDef.get("propertyName").toString();
final var propUnit = ofNullable(propDef.get("unit")).map(u -> " " + u).orElse("");
final var totalValue = ofNullable(assetEntity.getSubBookingItems()).orElse(emptyList())
.stream()
.filter(subItem -> subTypes.contains(subItem.getType()))
.map(subItem -> toInteger(subItem.getResources().get(propName)))
.reduce(0, Integer::sum);
final var maxValue = toInteger(assetEntity.getResources().get(propName));
return totalValue > maxValue
? "total %s is %d%s exceeds max total %s %d%s".formatted(
propName, totalValue, propUnit, propName, maxValue, propUnit)
: null;
}
protected List<String> validateParentEntities(final HsBookingItemEntity bookingItem) { protected List<String> validateParentEntities(final HsBookingItemEntity bookingItem) {
return bookingItem.getParentItem() != null return bookingItem.getParentItem() != null
? HsBookingItemEntityValidators.validate(bookingItem.getParentItem()) ? HsBookingItemEntityValidator.doValidate(bookingItem.getParentItem())
: emptyList(); : emptyList();
} }
private Integer toInteger(final Object value) {
return value == null ? 0 : (Integer) value;
}
} }

View File

@ -1,62 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
public abstract class HsPropertyValidator<T> {
final Class<T> type;
final String propertyName;
private Boolean required;
public HsPropertyValidator<T> required() {
required = Boolean.TRUE;
return this;
}
public HsPropertyValidator<T> optional() {
required = Boolean.FALSE;
return this;
}
public final List<String> validate(final String propertiesName, final Map<String, Object> props) {
final var result = new ArrayList<String>();
final var propValue = props.get(propertyName);
if (propValue == null) {
if (required) {
result.add("'"+propertiesName+"." + propertyName + "' is required but missing");
}
}
if (propValue != null){
if ( type.isInstance(propValue)) {
//noinspection unchecked
validate(result, propertiesName, (T) propValue, props);
} else {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be of type " + type + ", " +
"but is of type '" + propValue.getClass().getSimpleName() + "'");
}
}
return result;
}
protected abstract void validate(final ArrayList<String> result, final String propertiesName, final T propValue, final Map<String, Object> props);
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
if (required == null ) {
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" );
}
}
public Map<String, Object> toMap(final ObjectMapper mapper) {
final Map<String, Object> map = mapper.convertValue(this, Map.class);
map.put("type", simpleTypeName());
return map;
}
protected abstract String simpleTypeName();
}

View File

@ -0,0 +1,46 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Map;
@Setter
public class IntegerProperty extends ValidatableProperty<Integer> {
private String unit;
private Integer min;
private Integer max;
private Integer step;
public static IntegerProperty integerProperty(final String propertyName) {
return new IntegerProperty(propertyName);
}
private IntegerProperty(final String propertyName) {
super(Integer.class, propertyName);
}
@Override
public String unit() {
return unit;
}
@Override
protected void validate(final ArrayList<String> result, final Integer propValue, final Map<String, Object> props) {
if (min != null && propValue < min) {
result.add(propertyName + " is expected to be >= " + min + " but is " + propValue);
}
if (max != null && propValue > max) {
result.add(propertyName + " is expected to be <= " + max + " but is " + propValue);
}
if (step != null && propValue % step != 0) {
result.add(propertyName + " is expected to be multiple of " + step + " but is " + propValue);
}
}
@Override
protected String simpleTypeName() {
return "integer";
}
}

View File

@ -1,42 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Map;
@Setter
public class IntegerPropertyValidator extends HsPropertyValidator<Integer> {
private String unit;
private Integer min;
private Integer max;
private Integer step;
public static IntegerPropertyValidator integerProperty(final String propertyName) {
return new IntegerPropertyValidator(propertyName);
}
private IntegerPropertyValidator(final String propertyName) {
super(Integer.class, propertyName);
}
@Override
protected void validate(final ArrayList<String> result, final String propertiesName, final Integer propValue, final Map<String, Object> props) {
if (min != null && propValue < min) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be >= " + min + " but is " + propValue);
}
if (max != null && propValue > max) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be <= " + max + " but is " + propValue);
}
if (step != null && propValue % step != 0) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be multiple of " + step + " but is " + propValue);
}
}
@Override
protected String simpleTypeName() {
return "integer";
}
}

View File

@ -1,13 +0,0 @@
package net.hostsharing.hsadminng.hs.validation;
import java.util.Map;
public interface Validatable<E, T extends Enum<T>> {
Enum<T> getType();
String getPropertiesName();
Map<String, Object> getProperties();
}

View File

@ -0,0 +1,109 @@
package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.emptyList;
@RequiredArgsConstructor
public abstract class ValidatableProperty<T> {
final Class<T> type;
final String propertyName;
private Boolean required;
private T defaultValue;
private Supplier<List<String>> asTotalLimitValidator;
public String unit() {
return null;
}
public ValidatableProperty<T> required() {
required = TRUE;
return this;
}
public ValidatableProperty<T> optional() {
required = FALSE;
return this;
}
public ValidatableProperty<T> withDefault(final T value) {
defaultValue = value;
required = FALSE;
return this;
}
public ValidatableProperty<T> asTotalLimit() {
asTotalLimitValidator = ValidatableProperty::directTotalLimitValidator;
return this;
}
public String propertyName() {
return propertyName;
}
public boolean isTotalsValidator() {
return asTotalLimitValidator != null;
}
public ValidatableProperty<T> asTotalLimitFor(final T factor, final Supplier<List<String>> validator) {
// FIXME: implement i
asTotalLimitValidator = validator; // FIXME: implement multiple
return this;
}
public final List<String> validate(final Map<String, Object> props) {
final var result = new ArrayList<String>();
final var propValue = props.get(propertyName);
if (propValue == null) {
if (required) {
result.add(propertyName + " is required but missing");
}
}
if (propValue != null){
if ( type.isInstance(propValue)) {
//noinspection unchecked
validate(result, (T) propValue, props);
} else {
result.add(propertyName + " is expected to be of type " + type + ", " +
"but is of type '" + propValue.getClass().getSimpleName() + "'");
}
}
return result;
}
protected abstract void validate(final ArrayList<String> result, final T propValue, final Map<String, Object> props);
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
if (required == null ) {
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" );
}
}
private static List<String> directTotalLimitValidator() {
return emptyList(); // FIXME: implement
}
public Map<String, Object> toMap(final ObjectMapper mapper) {
final Map<String, Object> map = mapper.convertValue(this, Map.class);
map.put("type", simpleTypeName());
return map;
}
@SuppressWarnings("unchecked")
public T getValue(final Map<String, Object> propValues) {
return (T) Optional.ofNullable(propValues.get(propertyName)).orElse(defaultValue);
}
protected abstract String simpleTypeName();
}

View File

@ -1,6 +1,8 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; 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.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
@ -9,17 +11,26 @@ import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVAT
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.valid; import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.valid;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable; import static org.assertj.core.api.Assertions.catchThrowable;
class HsBookingItemEntityValidatorsUnitTest { class HsBookingItemEntityValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("test project")
.build();
@Test @Test
void validThrowsException() { void validThrowsException() {
// given // given
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder() final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.project(project)
.caption("Test-Server")
.build(); .build();
// when // when
@ -28,16 +39,16 @@ class HsBookingItemEntityValidatorsUnitTest {
// then // then
assertThat(result).isInstanceOf(ValidationException.class) assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining( .hasMessageContaining(
"'resources.CPUs' is required but missing", "D-12345:test project:Test-Server.resources.CPUs is required but missing",
"'resources.RAM' is required but missing", "D-12345:test project:Test-Server.resources.RAM is required but missing",
"'resources.SSD' is required but missing", "D-12345:test project:Test-Server.resources.SSD is required but missing",
"'resources.Traffic' is required but missing"); "D-12345:test project:Test-Server.resources.Traffic is required but missing");
} }
@Test @Test
void listsTypes() { void listsTypes() {
// when // when
final var result = HsBookingItemEntityValidators.types(); final var result = HsBookingItemEntityValidator.types();
// then // then
assertThat(result).containsExactlyInAnyOrder(PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE); assertThat(result).containsExactlyInAnyOrder(PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE);

View File

@ -1,6 +1,8 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; 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.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Map; import java.util.Map;
@ -11,16 +13,26 @@ import static java.util.Map.ofEntries;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType; import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.forType;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsCloudServerBookingItemValidatorUnitTest { class HsCloudServerBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test @Test
void validatesProperties() { void validatesProperties() {
// given // given
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder() final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.project(project)
.caption("Test-Server")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 25), entry("RAM", 25),
@ -31,10 +43,10 @@ class HsCloudServerBookingItemValidatorUnitTest {
.build(); .build();
// when // when
final var result = HsBookingItemEntityValidators.validate(cloudServerBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(cloudServerBookingItemEntity);
// then // then
assertThat(result).containsExactly("'resources.SLA-EMail' is not expected but is set to 'true'"); assertThat(result).containsExactly("D-12345:Test-Project:Test-Server.resources.SLA-EMail is not expected but is set to 'true'");
} }
@Test @Test
@ -44,12 +56,12 @@ class HsCloudServerBookingItemValidatorUnitTest {
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}", "{type=integer, propertyName=CPUs, required=true, defaultValue=null, asTotalLimitValidator=null, unit=null, min=1, max=32, step=null, totalsValidator=false}",
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}", "{type=integer, propertyName=RAM, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=1, max=128, step=null, totalsValidator=false}",
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}", "{type=integer, propertyName=SSD, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=25, max=1000, step=25, totalsValidator=false}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}", "{type=integer, propertyName=HDD, required=false, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=4000, step=250, totalsValidator=false}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}", "{type=integer, propertyName=Traffic, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=250, max=10000, step=250, totalsValidator=false}",
"{type=enumeration, propertyName=SLA-Infrastructure, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}"); "{type=enumeration, propertyName=SLA-Infrastructure, required=false, defaultValue=null, asTotalLimitValidator=null, values=[BASIC, EXT8H, EXT4H, EXT2H], totalsValidator=false}");
} }
@Test @Test
@ -57,6 +69,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
// given // given
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder() final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.caption("Test Cloud-Server")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 10), entry("RAM", 10),
@ -66,6 +79,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.build(); .build();
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder() final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.caption("Test Managed-Server")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 3), entry("CPUs", 3),
entry("RAM", 20), entry("RAM", 20),
@ -75,6 +89,8 @@ class HsCloudServerBookingItemValidatorUnitTest {
.build(); .build();
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder() final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD) .type(PRIVATE_CLOUD)
.project(project)
.caption("Test Cloud")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 4), entry("CPUs", 4),
entry("RAM", 20), entry("RAM", 20),
@ -90,14 +106,14 @@ class HsCloudServerBookingItemValidatorUnitTest {
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity); subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
// when // when
final var result = HsBookingItemEntityValidators.validate(subCloudServerBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(subCloudServerBookingItemEntity);
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"total CPUs is 5 exceeds max total CPUs 4", "D-12345:Test-Project:Test Cloud-Server.parentItem.total CPUs is 5 exceeds max total CPUs 4",
"total RAM is 30 GB exceeds max total RAM 20 GB", "D-12345:Test-Project:Test Cloud-Server.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
"total SSD is 150 GB exceeds max total SSD 100 GB", "D-12345:Test-Project:Test Cloud-Server.parentItem.total SSD is 150 GB exceeds max total SSD 100 GB",
"total Traffic is 5500 GB exceeds max total Traffic 5000 GB" "D-12345:Test-Project:Test Cloud-Server.parentItem.total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
); );
} }
} }

View File

@ -1,6 +1,8 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; 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.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Map; import java.util.Map;
@ -11,16 +13,25 @@ import static java.util.Map.ofEntries;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType; import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.forType;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsManagedServerBookingItemValidatorUnitTest { class HsManagedServerBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test @Test
void validatesProperties() { void validatesProperties() {
// given // given
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder() final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.project(project)
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 25), entry("RAM", 25),
@ -31,10 +42,10 @@ class HsManagedServerBookingItemValidatorUnitTest {
.build(); .build();
// when // when
final var result = HsBookingItemEntityValidators.validate(mangedServerBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
// then // then
assertThat(result).containsExactly("'resources.SLA-EMail' is expected to be false because resources.SLA-Platform=BASIC but is true"); assertThat(result).containsExactly("D-12345:Test-Project:null.resources.SLA-EMail is expected to be false because SLA-Platform=BASIC but is true");
} }
@Test @Test
@ -44,17 +55,17 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}", "{type=integer, propertyName=CPUs, required=true, defaultValue=null, asTotalLimitValidator=null, unit=null, min=1, max=32, step=null, totalsValidator=false}",
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}", "{type=integer, propertyName=RAM, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=1, max=128, step=null, totalsValidator=false}",
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}", "{type=integer, propertyName=SSD, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=25, max=1000, step=25, totalsValidator=false}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}", "{type=integer, propertyName=HDD, required=false, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=4000, step=250, totalsValidator=false}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}", "{type=integer, propertyName=Traffic, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=250, max=10000, step=250, totalsValidator=false}",
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}", "{type=enumeration, propertyName=SLA-Platform, required=false, defaultValue=null, asTotalLimitValidator=null, values=[BASIC, EXT8H, EXT4H, EXT2H], totalsValidator=false}",
"{type=boolean, propertyName=SLA-EMail, required=false, falseIf={SLA-Platform=BASIC}}", "{type=boolean, propertyName=SLA-EMail, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
"{type=boolean, propertyName=SLA-Maria, required=false, falseIf={SLA-Platform=BASIC}}", "{type=boolean, propertyName=SLA-Maria, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
"{type=boolean, propertyName=SLA-PgSQL, required=false, falseIf={SLA-Platform=BASIC}}", "{type=boolean, propertyName=SLA-PgSQL, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
"{type=boolean, propertyName=SLA-Office, required=false, falseIf={SLA-Platform=BASIC}}", "{type=boolean, propertyName=SLA-Office, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
"{type=boolean, propertyName=SLA-Web, required=false, falseIf={SLA-Platform=BASIC}}"); "{type=boolean, propertyName=SLA-Web, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}");
} }
@Test @Test
@ -80,6 +91,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
.build(); .build();
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder() final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD) .type(PRIVATE_CLOUD)
.project(project)
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 4), entry("CPUs", 4),
entry("RAM", 20), entry("RAM", 20),
@ -91,18 +103,19 @@ class HsManagedServerBookingItemValidatorUnitTest {
subCloudServerBookingItemEntity subCloudServerBookingItemEntity
)) ))
.build(); .build();
subManagedServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity); subManagedServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity); subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
// when // when
final var result = HsBookingItemEntityValidators.validate(subManagedServerBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(subManagedServerBookingItemEntity);
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"total CPUs is 5 exceeds max total CPUs 4", "D-12345:Test-Project:null.parentItem.total CPUs is 5 exceeds max total CPUs 4",
"total RAM is 30 GB exceeds max total RAM 20 GB", "D-12345:Test-Project:null.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
"total SSD is 150 GB exceeds max total SSD 100 GB", "D-12345:Test-Project:null.parentItem.total SSD is 150 GB exceeds max total SSD 100 GB",
"total Traffic is 5500 GB exceeds max total Traffic 5000 GB" "D-12345:Test-Project:null.parentItem.total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
); );
} }
} }

View File

@ -1,6 +1,8 @@
package net.hostsharing.hsadminng.hs.booking.item.validators; 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.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Map; import java.util.Map;
@ -11,11 +13,21 @@ import static org.assertj.core.api.Assertions.assertThat;
class HsManagedWebspaceBookingItemValidatorUnitTest { class HsManagedWebspaceBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test @Test
void validatesProperties() { void validatesProperties() {
// given // given
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder() final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_WEBSPACE) .type(MANAGED_WEBSPACE)
.project(project)
.caption("Test Managed-Webspace")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPUs", 2),
entry("RAM", 25), entry("RAM", 25),
@ -26,27 +38,29 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
.build(); .build();
// when // when
final var result = HsBookingItemEntityValidators.validate(mangedServerBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'resources.CPUs' is not expected but is set to '2'", "D-12345:Test-Project:Test Managed-Webspace.resources.SLA-EMail is not expected but is set to 'true'",
"'resources.SLA-EMail' is not expected but is set to 'true'", "D-12345:Test-Project:Test Managed-Webspace.resources.CPUs is not expected but is set to '2'",
"'resources.RAM' is not expected but is set to '25'"); "D-12345:Test-Project:Test Managed-Webspace.resources.RAM is not expected but is set to '25'",
"D-12345:Test-Project:Test Managed-Webspace.resources.MultiOptions is required but missing");
} }
@Test @Test
void containsAllValidations() { void containsAllValidations() {
// when // when
final var validator = HsBookingItemEntityValidators.forType(MANAGED_WEBSPACE); final var validator = HsBookingItemEntityValidator.forType(MANAGED_WEBSPACE);
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=SSD, required=true, unit=GB, min=1, max=100, step=1}", "{type=integer, propertyName=SSD, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=1, max=100, step=1, totalsValidator=false}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=250, step=10}", "{type=integer, propertyName=HDD, required=false, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=250, step=10, totalsValidator=false}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=10, max=1000, step=10}", "{type=integer, propertyName=Traffic, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=10, max=1000, step=10, totalsValidator=false}",
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT24H]}", "{type=integer, propertyName=MultiOptions, required=true, defaultValue=null, asTotalLimitValidator={}, unit=null, min=1, max=100, step=1, totalsValidator=true}",
"{type=integer, propertyName=Daemons, required=false, unit=null, min=0, max=10, step=null}", "{type=integer, propertyName=Daemons, required=false, defaultValue=null, asTotalLimitValidator=null, unit=null, min=0, max=10, step=null, totalsValidator=false}",
"{type=boolean, propertyName=Online Office Server, required=false, falseIf=null}"); "{type=boolean, propertyName=Online Office Server, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf=null, totalsValidator=false}",
"{type=enumeration, propertyName=SLA-Platform, required=false, defaultValue=null, asTotalLimitValidator=null, values=[BASIC, EXT24H], totalsValidator=false}");
} }
} }

View File

@ -48,7 +48,7 @@ class HsPrivateCloudBookingItemValidatorTest {
.build(); .build();
// when // when
final var result = HsBookingItemEntityValidators.validate(privateCloudBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
// then // then
assertThat(result).isEmpty(); assertThat(result).isEmpty();
@ -88,7 +88,7 @@ class HsPrivateCloudBookingItemValidatorTest {
.build(); .build();
// when // when
final var result = HsBookingItemEntityValidators.validate(privateCloudBookingItemEntity); final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(

View File

@ -1,13 +1,14 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Map; import java.util.Map;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT; import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
@ -16,14 +17,15 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder() final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder()
.project(TEST_PROJECT) .project(TEST_PROJECT)
.type(HsBookingItemType.MANAGED_SERVER)
.build(); .build();
final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder() final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_SERVER) .type(HsHostingAssetType.MANAGED_SERVER)
.bookingItem(managedServerBookingItem) .bookingItem(managedServerBookingItem)
.config(Map.ofEntries( .config(Map.ofEntries(
entry("HDD", 0), entry("monit_max_ssd_usage", 70),
entry("SSD", 1), entry("monit_max_cpu_usage", 80),
entry("Traffic", 10) entry("monit_max_ram_usage", 90)
)) ))
.build(); .build();