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.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;
@RestController

View File

@ -10,7 +10,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
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.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -65,7 +64,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatable<HsBookingItemEntity, HsBookingItemType> {
public class HsBookingItemEntity implements Stringifyable, RbacObject {
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
.withProp(HsBookingItemEntity::getProject)
@ -157,16 +156,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
return parentItem == null ? null : parentItem.relatedProject();
}
@Override
public String getPropertiesName() {
return "resources";
}
@Override
public Map<String, Object> getProperties() {
return resources;
}
public HsBookingProjectEntity 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;
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 static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
class HsCloudServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator {
HsCloudServerBookingItemValidator() {
super(

View File

@ -1,16 +1,14 @@
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 java.util.List;
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
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;
class HsManagedServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
class HsManagedServerBookingItemValidator extends HsBookingItemEntityValidator {
HsManagedServerBookingItemValidator() {
super(

View File

@ -1,24 +1,47 @@
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 java.util.List;
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;
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
class HsManagedWebspaceBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator {
public HsManagedWebspaceBookingItemValidator() {
super(
integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(),
integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(),
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(),
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;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
import java.util.List;
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> {
class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator {
HsPrivateCloudBookingItemValidator() {
super(
integerProperty("CPUs").min(4).max(128).required(),
integerProperty("RAM").unit("GB").min(4).max(512).required(),
integerProperty("SSD").unit("GB").min(100).max(4000).step(25).required(),
integerProperty("HDD").unit("GB").min(0).max(16000).step(25).optional(),
integerProperty("Traffic").unit("GB").min(1000).max(40000).step(250).required(),
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional()
integerProperty("CPUs").min(4).max(128).required().asTotalLimit(),
integerProperty("RAM").unit("GB").min(4).max(512).required().asTotalLimit(),
integerProperty("SSD").unit("GB").min(100).max(4000).step(25).required().asTotalLimit(),
integerProperty("HDD").unit("GB").min(0).max(16000).step(25).withDefault(0).asTotalLimit(),
integerProperty("Traffic").unit("GB").min(1000).max(40000).step(250).required().asTotalLimit(),
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.Setter;
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.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -56,7 +55,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validatable<HsHostingAssetEntity, HsHostingAssetType> {
public class HsHostingAssetEntity implements Stringifyable, RbacObject {
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
.withProp(HsHostingAssetEntity::getType)
@ -114,16 +113,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg);
}
@Override
public String getPropertiesName() {
return "config";
}
@Override
public Map<String, Object> getProperties() {
return config;
}
@Override
public String toString() {
return stringify.apply(this);

View File

@ -1,10 +1,11 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
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.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import org.apache.commons.collections4.ListUtils;
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_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 {
register(CLOUD_SERVER, new HsEntityValidator<>());
register(CLOUD_SERVER, new HsHostingAssetEntityValidator());
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
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 -> {
entry.verifyConsistency(Map.entry(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);
}
@ -51,9 +52,15 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
return entityToSave;
}
@SafeVarargs
public HsHostingAssetEntityValidator(final ValidatableProperty<?>... properties) {
super(properties);
}
@Override
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()
// higher levels are only validated with valid sub-entity
? ListUtils.union(
@ -67,6 +74,6 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
}
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;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
class HsManagedServerHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator {
public HsManagedServerHostingAssetValidator() {
super(

View File

@ -1,13 +1,11 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
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;
class HsManagedWebspaceHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator {
public HsManagedWebspaceHostingAssetValidator() {
}

View File

@ -8,29 +8,29 @@ import java.util.Map;
import java.util.Objects;
@Setter
public class BooleanPropertyValidator extends HsPropertyValidator<Boolean> {
public class BooleanProperty extends ValidatableProperty<Boolean> {
private Map.Entry<String, String> falseIf;
private BooleanPropertyValidator(final String propertyName) {
private BooleanProperty(final String propertyName) {
super(Boolean.class, propertyName);
}
public static BooleanPropertyValidator booleanProperty(final String propertyName) {
return new BooleanPropertyValidator(propertyName);
public static BooleanProperty booleanProperty(final String 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);
return this;
}
@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 (propValue) {
result.add("'"+propertiesName+"." + propertyName + "' is expected to be false because " +
propertiesName+"." + falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue);
result.add(propertyName + " is expected to be false because " +
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.databind.ObjectMapper;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static java.util.Arrays.stream;
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;
}
public List<String> validate(final E assetEntity) {
final var result = new ArrayList<String>();
assetEntity.getProperties().keySet().forEach( givenPropName -> {
if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) {
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 List<String> enrich(final String prefix, final List<String> messages) {
return messages.stream()
.map(message -> prefix + "." + message)
.toList();
}
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() {
final var mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
@ -48,45 +43,27 @@ public class HsEntityValidator<E extends Validatable<E, T>, T extends Enum<T>> {
.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" })
private static Map<String, Object> asKeyValueMap(final Map 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) {
return bookingItem.getParentItem() != null
? HsBookingItemEntityValidators.validate(bookingItem.getParentItem())
? HsBookingItemEntityValidator.doValidate(bookingItem.getParentItem())
: 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;
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 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.MANAGED_SERVER;
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.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
void validThrowsException() {
// given
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.project(project)
.caption("Test-Server")
.build();
// when
@ -28,16 +39,16 @@ class HsBookingItemEntityValidatorsUnitTest {
// then
assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining(
"'resources.CPUs' is required but missing",
"'resources.RAM' is required but missing",
"'resources.SSD' is required but missing",
"'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
void listsTypes() {
// when
final var result = HsBookingItemEntityValidators.types();
final var result = HsBookingItemEntityValidator.types();
// then
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;
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 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.MANAGED_SERVER;
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;
class HsCloudServerBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test
void validatesProperties() {
// given
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.project(project)
.caption("Test-Server")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("RAM", 25),
@ -31,10 +43,10 @@ class HsCloudServerBookingItemValidatorUnitTest {
.build();
// when
final var result = HsBookingItemEntityValidators.validate(cloudServerBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(cloudServerBookingItemEntity);
// 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
@ -44,12 +56,12 @@ class HsCloudServerBookingItemValidatorUnitTest {
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}",
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}",
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}",
"{type=enumeration, propertyName=SLA-Infrastructure, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}");
"{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, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=1, max=128, step=null, totalsValidator=false}",
"{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, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=4000, step=250, totalsValidator=false}",
"{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, defaultValue=null, asTotalLimitValidator=null, values=[BASIC, EXT8H, EXT4H, EXT2H], totalsValidator=false}");
}
@Test
@ -57,6 +69,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
// given
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER)
.caption("Test Cloud-Server")
.resources(ofEntries(
entry("CPUs", 2),
entry("RAM", 10),
@ -66,6 +79,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.build();
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.caption("Test Managed-Server")
.resources(ofEntries(
entry("CPUs", 3),
entry("RAM", 20),
@ -75,6 +89,8 @@ class HsCloudServerBookingItemValidatorUnitTest {
.build();
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.project(project)
.caption("Test Cloud")
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
@ -90,14 +106,14 @@ class HsCloudServerBookingItemValidatorUnitTest {
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
// when
final var result = HsBookingItemEntityValidators.validate(subCloudServerBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(subCloudServerBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"total CPUs is 5 exceeds max total CPUs 4",
"total RAM is 30 GB exceeds max total RAM 20 GB",
"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 CPUs is 5 exceeds max total CPUs 4",
"D-12345:Test-Project:Test Cloud-Server.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
"D-12345:Test-Project:Test Cloud-Server.parentItem.total SSD is 150 GB exceeds max total SSD 100 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;
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 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.MANAGED_SERVER;
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;
class HsManagedServerBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test
void validatesProperties() {
// given
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER)
.project(project)
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("RAM", 25),
@ -31,10 +42,10 @@ class HsManagedServerBookingItemValidatorUnitTest {
.build();
// when
final var result = HsBookingItemEntityValidators.validate(mangedServerBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
// 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
@ -44,17 +55,17 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}",
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}",
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}",
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}",
"{type=boolean, propertyName=SLA-EMail, required=false, falseIf={SLA-Platform=BASIC}}",
"{type=boolean, propertyName=SLA-Maria, required=false, falseIf={SLA-Platform=BASIC}}",
"{type=boolean, propertyName=SLA-PgSQL, required=false, falseIf={SLA-Platform=BASIC}}",
"{type=boolean, propertyName=SLA-Office, required=false, falseIf={SLA-Platform=BASIC}}",
"{type=boolean, propertyName=SLA-Web, required=false, falseIf={SLA-Platform=BASIC}}");
"{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, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=1, max=128, step=null, totalsValidator=false}",
"{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, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=4000, step=250, totalsValidator=false}",
"{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, defaultValue=null, asTotalLimitValidator=null, values=[BASIC, EXT8H, EXT4H, EXT2H], totalsValidator=false}",
"{type=boolean, propertyName=SLA-EMail, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
"{type=boolean, propertyName=SLA-Maria, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
"{type=boolean, propertyName=SLA-PgSQL, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
"{type=boolean, propertyName=SLA-Office, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
"{type=boolean, propertyName=SLA-Web, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}");
}
@Test
@ -80,6 +91,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
.build();
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD)
.project(project)
.resources(ofEntries(
entry("CPUs", 4),
entry("RAM", 20),
@ -91,18 +103,19 @@ class HsManagedServerBookingItemValidatorUnitTest {
subCloudServerBookingItemEntity
))
.build();
subManagedServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
// when
final var result = HsBookingItemEntityValidators.validate(subManagedServerBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(subManagedServerBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"total CPUs is 5 exceeds max total CPUs 4",
"total RAM is 30 GB exceeds max total RAM 20 GB",
"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 CPUs is 5 exceeds max total CPUs 4",
"D-12345:Test-Project:null.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
"D-12345:Test-Project:null.parentItem.total SSD is 150 GB exceeds max total SSD 100 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;
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 java.util.Map;
@ -11,11 +13,21 @@ import static org.assertj.core.api.Assertions.assertThat;
class HsManagedWebspaceBookingItemValidatorUnitTest {
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
.debitorNumber(12345)
.build();
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
.debitor(debitor)
.caption("Test-Project")
.build();
@Test
void validatesProperties() {
// given
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_WEBSPACE)
.project(project)
.caption("Test Managed-Webspace")
.resources(Map.ofEntries(
entry("CPUs", 2),
entry("RAM", 25),
@ -26,27 +38,29 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
.build();
// when
final var result = HsBookingItemEntityValidators.validate(mangedServerBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'resources.CPUs' is not expected but is set to '2'",
"'resources.SLA-EMail' is not expected but is set to 'true'",
"'resources.RAM' is not expected but is set to '25'");
"D-12345:Test-Project:Test Managed-Webspace.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'",
"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
void containsAllValidations() {
// when
final var validator = HsBookingItemEntityValidators.forType(MANAGED_WEBSPACE);
final var validator = HsBookingItemEntityValidator.forType(MANAGED_WEBSPACE);
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=SSD, required=true, unit=GB, min=1, max=100, step=1}",
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=250, step=10}",
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=10, max=1000, step=10}",
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT24H]}",
"{type=integer, propertyName=Daemons, required=false, unit=null, min=0, max=10, step=null}",
"{type=boolean, propertyName=Online Office Server, required=false, falseIf=null}");
"{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, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=250, step=10, totalsValidator=false}",
"{type=integer, propertyName=Traffic, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=10, max=1000, step=10, totalsValidator=false}",
"{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, defaultValue=null, asTotalLimitValidator=null, unit=null, min=0, max=10, step=null, totalsValidator=false}",
"{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();
// when
final var result = HsBookingItemEntityValidators.validate(privateCloudBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
// then
assertThat(result).isEmpty();
@ -88,7 +88,7 @@ class HsPrivateCloudBookingItemValidatorTest {
.build();
// when
final var result = HsBookingItemEntityValidators.validate(privateCloudBookingItemEntity);
final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
// then
assertThat(result).containsExactlyInAnyOrder(

View File

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